init
This commit is contained in:
commit
38355d2442
9083 changed files with 1225834 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1221
.venv/lib/python3.8/site-packages/mypyc/irbuild/builder.py
Normal file
1221
.venv/lib/python3.8/site-packages/mypyc/irbuild/builder.py
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
|
@ -0,0 +1,164 @@
|
|||
"""Generate a class that represents a nested function.
|
||||
|
||||
The class defines __call__ for calling the function and allows access to
|
||||
non-local variables defined in outer scopes.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
|
||||
from mypyc.common import SELF_NAME, ENV_ATTR_NAME
|
||||
from mypyc.ir.ops import BasicBlock, Return, Call, SetAttr, Value, Register
|
||||
from mypyc.ir.rtypes import RInstance, object_rprimitive
|
||||
from mypyc.ir.func_ir import FuncIR, FuncSignature, RuntimeArg, FuncDecl
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
from mypyc.irbuild.context import FuncInfo, ImplicitClass
|
||||
from mypyc.primitives.misc_ops import method_new_op
|
||||
|
||||
|
||||
def setup_callable_class(builder: IRBuilder) -> None:
|
||||
"""Generate an (incomplete) callable class representing function.
|
||||
|
||||
This can be a nested function or a function within a non-extension
|
||||
class. Also set up the 'self' variable for that class.
|
||||
|
||||
This takes the most recently visited function and returns a
|
||||
ClassIR to represent that function. Each callable class contains
|
||||
an environment attribute which points to another ClassIR
|
||||
representing the environment class where some of its variables can
|
||||
be accessed.
|
||||
|
||||
Note that some methods, such as '__call__', are not yet
|
||||
created here. Use additional functions, such as
|
||||
add_call_to_callable_class(), to add them.
|
||||
|
||||
Return a newly constructed ClassIR representing the callable
|
||||
class for the nested function.
|
||||
"""
|
||||
# Check to see that the name has not already been taken. If so,
|
||||
# rename the class. We allow multiple uses of the same function
|
||||
# name because this is valid in if-else blocks. Example:
|
||||
#
|
||||
# if True:
|
||||
# def foo(): ----> foo_obj()
|
||||
# return True
|
||||
# else:
|
||||
# def foo(): ----> foo_obj_0()
|
||||
# return False
|
||||
name = base_name = '{}_obj'.format(builder.fn_info.namespaced_name())
|
||||
count = 0
|
||||
while name in builder.callable_class_names:
|
||||
name = base_name + '_' + str(count)
|
||||
count += 1
|
||||
builder.callable_class_names.add(name)
|
||||
|
||||
# Define the actual callable class ClassIR, and set its
|
||||
# environment to point at the previously defined environment
|
||||
# class.
|
||||
callable_class_ir = ClassIR(name, builder.module_name, is_generated=True)
|
||||
|
||||
# The functools @wraps decorator attempts to call setattr on
|
||||
# nested functions, so we create a dict for these nested
|
||||
# functions.
|
||||
# https://github.com/python/cpython/blob/3.7/Lib/functools.py#L58
|
||||
if builder.fn_info.is_nested:
|
||||
callable_class_ir.has_dict = True
|
||||
|
||||
# If the enclosing class doesn't contain nested (which will happen if
|
||||
# this is a toplevel lambda), don't set up an environment.
|
||||
if builder.fn_infos[-2].contains_nested:
|
||||
callable_class_ir.attributes[ENV_ATTR_NAME] = RInstance(
|
||||
builder.fn_infos[-2].env_class
|
||||
)
|
||||
callable_class_ir.mro = [callable_class_ir]
|
||||
builder.fn_info.callable_class = ImplicitClass(callable_class_ir)
|
||||
builder.classes.append(callable_class_ir)
|
||||
|
||||
# Add a 'self' variable to the environment of the callable class,
|
||||
# and store that variable in a register to be accessed later.
|
||||
self_target = builder.add_self_to_env(callable_class_ir)
|
||||
builder.fn_info.callable_class.self_reg = builder.read(self_target, builder.fn_info.fitem.line)
|
||||
|
||||
|
||||
def add_call_to_callable_class(builder: IRBuilder,
|
||||
args: List[Register],
|
||||
blocks: List[BasicBlock],
|
||||
sig: FuncSignature,
|
||||
fn_info: FuncInfo) -> FuncIR:
|
||||
"""Generate a '__call__' method for a callable class representing a nested function.
|
||||
|
||||
This takes the blocks and signature associated with a function
|
||||
definition and uses those to build the '__call__' method of a
|
||||
given callable class, used to represent that function.
|
||||
"""
|
||||
# Since we create a method, we also add a 'self' parameter.
|
||||
sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive),) + sig.args, sig.ret_type)
|
||||
call_fn_decl = FuncDecl('__call__', fn_info.callable_class.ir.name, builder.module_name, sig)
|
||||
call_fn_ir = FuncIR(call_fn_decl, args, blocks,
|
||||
fn_info.fitem.line, traceback_name=fn_info.fitem.name)
|
||||
fn_info.callable_class.ir.methods['__call__'] = call_fn_ir
|
||||
fn_info.callable_class.ir.method_decls['__call__'] = call_fn_decl
|
||||
return call_fn_ir
|
||||
|
||||
|
||||
def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
|
||||
"""Generate the '__get__' method for a callable class."""
|
||||
line = fn_info.fitem.line
|
||||
with builder.enter_method(
|
||||
fn_info.callable_class.ir, '__get__', object_rprimitive, fn_info,
|
||||
self_type=object_rprimitive):
|
||||
instance = builder.add_argument('instance', object_rprimitive)
|
||||
builder.add_argument('owner', object_rprimitive)
|
||||
|
||||
# If accessed through the class, just return the callable
|
||||
# object. If accessed through an object, create a new bound
|
||||
# instance method object.
|
||||
instance_block, class_block = BasicBlock(), BasicBlock()
|
||||
comparison = builder.translate_is_op(
|
||||
builder.read(instance), builder.none_object(), 'is', line
|
||||
)
|
||||
builder.add_bool_branch(comparison, class_block, instance_block)
|
||||
|
||||
builder.activate_block(class_block)
|
||||
builder.add(Return(builder.self()))
|
||||
|
||||
builder.activate_block(instance_block)
|
||||
builder.add(Return(builder.call_c(method_new_op,
|
||||
[builder.self(), builder.read(instance)], line)))
|
||||
|
||||
|
||||
def instantiate_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> Value:
|
||||
"""Create an instance of a callable class for a function.
|
||||
|
||||
Calls to the function will actually call this instance.
|
||||
|
||||
Note that fn_info refers to the function being assigned, whereas
|
||||
builder.fn_info refers to the function encapsulating the function
|
||||
being turned into a callable class.
|
||||
"""
|
||||
fitem = fn_info.fitem
|
||||
func_reg = builder.add(Call(fn_info.callable_class.ir.ctor, [], fitem.line))
|
||||
|
||||
# Set the environment attribute of the callable class to point at
|
||||
# the environment class defined in the callable class' immediate
|
||||
# outer scope. Note that there are three possible environment
|
||||
# class registers we may use. This depends on what the encapsulating
|
||||
# (parent) function is:
|
||||
#
|
||||
# - A nested function: the callable class is instantiated
|
||||
# from the current callable class' '__call__' function, and hence
|
||||
# the callable class' environment register is used.
|
||||
# - A generator function: the callable class is instantiated
|
||||
# from the '__next__' method of the generator class, and hence the
|
||||
# environment of the generator class is used.
|
||||
# - Regular function: we use the environment of the original function.
|
||||
curr_env_reg = None
|
||||
if builder.fn_info.is_generator:
|
||||
curr_env_reg = builder.fn_info.generator_class.curr_env_reg
|
||||
elif builder.fn_info.is_nested:
|
||||
curr_env_reg = builder.fn_info.callable_class.curr_env_reg
|
||||
elif builder.fn_info.contains_nested:
|
||||
curr_env_reg = builder.fn_info.curr_env_reg
|
||||
if curr_env_reg:
|
||||
builder.add(SetAttr(func_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line))
|
||||
return func_reg
|
||||
Binary file not shown.
702
.venv/lib/python3.8/site-packages/mypyc/irbuild/classdef.py
Normal file
702
.venv/lib/python3.8/site-packages/mypyc/irbuild/classdef.py
Normal file
|
|
@ -0,0 +1,702 @@
|
|||
"""Transform class definitions from the mypy AST form to IR."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import Callable, List, Optional, Tuple
|
||||
from typing_extensions import Final
|
||||
|
||||
from mypy.nodes import (
|
||||
ClassDef, FuncDef, OverloadedFuncDef, PassStmt, AssignmentStmt, CallExpr, NameExpr, StrExpr,
|
||||
ExpressionStmt, TempNode, Decorator, Lvalue, MemberExpr, RefExpr, TypeInfo, is_class_var
|
||||
)
|
||||
from mypy.types import Instance, get_proper_type, ENUM_REMOVED_PROPS
|
||||
from mypyc.ir.ops import (
|
||||
Value, Register, Call, LoadErrorValue, LoadStatic, InitStatic, TupleSet, SetAttr, Return,
|
||||
BasicBlock, Branch, MethodCall, NAMESPACE_TYPE, LoadAddress
|
||||
)
|
||||
from mypyc.ir.rtypes import (
|
||||
RType, object_rprimitive, bool_rprimitive, dict_rprimitive, is_optional_type,
|
||||
is_object_rprimitive, is_none_rprimitive
|
||||
)
|
||||
from mypyc.ir.func_ir import FuncDecl, FuncSignature
|
||||
from mypyc.ir.class_ir import ClassIR, NonExtClassInfo
|
||||
from mypyc.primitives.generic_ops import py_setattr_op, py_hasattr_op
|
||||
from mypyc.primitives.misc_ops import (
|
||||
dataclass_sleight_of_hand, pytype_from_template_op, py_calc_meta_op, type_object_op,
|
||||
not_implemented_op
|
||||
)
|
||||
from mypyc.primitives.dict_ops import dict_set_item_op, dict_new_op
|
||||
from mypyc.irbuild.util import (
|
||||
is_dataclass_decorator, get_func_def, is_constant, dataclass_type
|
||||
)
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
from mypyc.irbuild.function import handle_ext_method, handle_non_ext_method, load_type
|
||||
|
||||
|
||||
def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None:
|
||||
"""Create IR for a class definition.
|
||||
|
||||
This can generate both extension (native) and non-extension
|
||||
classes. These are generated in very different ways. In the
|
||||
latter case we construct a Python type object at runtime by doing
|
||||
the equivalent of "type(name, bases, dict)" in IR. Extension
|
||||
classes are defined via C structs that are generated later in
|
||||
mypyc.codegen.emitclass.
|
||||
|
||||
This is the main entry point to this module.
|
||||
"""
|
||||
ir = builder.mapper.type_to_ir[cdef.info]
|
||||
|
||||
# We do this check here because the base field of parent
|
||||
# classes aren't necessarily populated yet at
|
||||
# prepare_class_def time.
|
||||
if any(ir.base_mro[i].base != ir. base_mro[i + 1] for i in range(len(ir.base_mro) - 1)):
|
||||
builder.error("Non-trait MRO must be linear", cdef.line)
|
||||
|
||||
if ir.allow_interpreted_subclasses:
|
||||
for parent in ir.mro:
|
||||
if not parent.allow_interpreted_subclasses:
|
||||
builder.error(
|
||||
'Base class "{}" does not allow interpreted subclasses'.format(
|
||||
parent.fullname), cdef.line)
|
||||
|
||||
# Currently, we only create non-extension classes for classes that are
|
||||
# decorated or inherit from Enum. Classes decorated with @trait do not
|
||||
# apply here, and are handled in a different way.
|
||||
if ir.is_ext_class:
|
||||
cls_type = dataclass_type(cdef)
|
||||
if cls_type is None:
|
||||
cls_builder: ClassBuilder = ExtClassBuilder(builder, cdef)
|
||||
elif cls_type in ['dataclasses', 'attr-auto']:
|
||||
cls_builder = DataClassBuilder(builder, cdef)
|
||||
elif cls_type == 'attr':
|
||||
cls_builder = AttrsClassBuilder(builder, cdef)
|
||||
else:
|
||||
raise ValueError(cls_type)
|
||||
else:
|
||||
cls_builder = NonExtClassBuilder(builder, cdef)
|
||||
|
||||
for stmt in cdef.defs.body:
|
||||
if isinstance(stmt, OverloadedFuncDef) and stmt.is_property:
|
||||
if isinstance(cls_builder, NonExtClassBuilder):
|
||||
# properties with both getters and setters in non_extension
|
||||
# classes not supported
|
||||
builder.error("Property setters not supported in non-extension classes",
|
||||
stmt.line)
|
||||
for item in stmt.items:
|
||||
with builder.catch_errors(stmt.line):
|
||||
cls_builder.add_method(get_func_def(item))
|
||||
elif isinstance(stmt, (FuncDef, Decorator, OverloadedFuncDef)):
|
||||
# Ignore plugin generated methods (since they have no
|
||||
# bodies to compile and will need to have the bodies
|
||||
# provided by some other mechanism.)
|
||||
if cdef.info.names[stmt.name].plugin_generated:
|
||||
continue
|
||||
with builder.catch_errors(stmt.line):
|
||||
cls_builder.add_method(get_func_def(stmt))
|
||||
elif isinstance(stmt, PassStmt):
|
||||
continue
|
||||
elif isinstance(stmt, AssignmentStmt):
|
||||
if len(stmt.lvalues) != 1:
|
||||
builder.error("Multiple assignment in class bodies not supported", stmt.line)
|
||||
continue
|
||||
lvalue = stmt.lvalues[0]
|
||||
if not isinstance(lvalue, NameExpr):
|
||||
builder.error("Only assignment to variables is supported in class bodies",
|
||||
stmt.line)
|
||||
continue
|
||||
# We want to collect class variables in a dictionary for both real
|
||||
# non-extension classes and fake dataclass ones.
|
||||
cls_builder.add_attr(lvalue, stmt)
|
||||
|
||||
elif isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr):
|
||||
# Docstring. Ignore
|
||||
pass
|
||||
else:
|
||||
builder.error("Unsupported statement in class body", stmt.line)
|
||||
|
||||
cls_builder.finalize(ir)
|
||||
|
||||
|
||||
class ClassBuilder:
|
||||
"""Create IR for a class definition.
|
||||
|
||||
This is an abstract base class.
|
||||
"""
|
||||
|
||||
def __init__(self, builder: IRBuilder, cdef: ClassDef) -> None:
|
||||
self.builder = builder
|
||||
self.cdef = cdef
|
||||
self.attrs_to_cache: List[Tuple[Lvalue, RType]] = []
|
||||
|
||||
@abstractmethod
|
||||
def add_method(self, fdef: FuncDef) -> None:
|
||||
"""Add a method to the class IR"""
|
||||
|
||||
@abstractmethod
|
||||
def add_attr(self, lvalue: NameExpr, stmt: AssignmentStmt) -> None:
|
||||
"""Add an attribute to the class IR"""
|
||||
|
||||
@abstractmethod
|
||||
def finalize(self, ir: ClassIR) -> None:
|
||||
"""Perform any final operations to complete the class IR"""
|
||||
|
||||
|
||||
class NonExtClassBuilder(ClassBuilder):
|
||||
def __init__(self, builder: IRBuilder, cdef: ClassDef) -> None:
|
||||
super().__init__(builder, cdef)
|
||||
self.non_ext = self.create_non_ext_info()
|
||||
|
||||
def create_non_ext_info(self) -> NonExtClassInfo:
|
||||
non_ext_bases = populate_non_ext_bases(self.builder, self.cdef)
|
||||
non_ext_metaclass = find_non_ext_metaclass(self.builder, self.cdef, non_ext_bases)
|
||||
non_ext_dict = setup_non_ext_dict(self.builder, self.cdef, non_ext_metaclass,
|
||||
non_ext_bases)
|
||||
# We populate __annotations__ for non-extension classes
|
||||
# because dataclasses uses it to determine which attributes to compute on.
|
||||
# TODO: Maybe generate more precise types for annotations
|
||||
non_ext_anns = self.builder.call_c(dict_new_op, [], self.cdef.line)
|
||||
return NonExtClassInfo(non_ext_dict, non_ext_bases, non_ext_anns, non_ext_metaclass)
|
||||
|
||||
def add_method(self, fdef: FuncDef) -> None:
|
||||
handle_non_ext_method(self.builder, self.non_ext, self.cdef, fdef)
|
||||
|
||||
def add_attr(self, lvalue: NameExpr, stmt: AssignmentStmt) -> None:
|
||||
add_non_ext_class_attr_ann(self.builder, self.non_ext, lvalue, stmt)
|
||||
add_non_ext_class_attr(self.builder, self.non_ext, lvalue, stmt, self.cdef,
|
||||
self.attrs_to_cache)
|
||||
|
||||
def finalize(self, ir: ClassIR) -> None:
|
||||
# Dynamically create the class via the type constructor
|
||||
non_ext_class = load_non_ext_class(self.builder, ir, self.non_ext, self.cdef.line)
|
||||
non_ext_class = load_decorated_class(self.builder, self.cdef, non_ext_class)
|
||||
|
||||
# Save the decorated class
|
||||
self.builder.add(InitStatic(non_ext_class, self.cdef.name, self.builder.module_name,
|
||||
NAMESPACE_TYPE))
|
||||
|
||||
# Add the non-extension class to the dict
|
||||
self.builder.call_c(dict_set_item_op,
|
||||
[
|
||||
self.builder.load_globals_dict(),
|
||||
self.builder.load_str(self.cdef.name),
|
||||
non_ext_class
|
||||
], self.cdef.line)
|
||||
|
||||
# Cache any cacheable class attributes
|
||||
cache_class_attrs(self.builder, self.attrs_to_cache, self.cdef)
|
||||
|
||||
|
||||
class ExtClassBuilder(ClassBuilder):
|
||||
def __init__(self, builder: IRBuilder, cdef: ClassDef) -> None:
|
||||
super().__init__(builder, cdef)
|
||||
# If the class is not decorated, generate an extension class for it.
|
||||
self.type_obj: Optional[Value] = allocate_class(builder, cdef)
|
||||
|
||||
def skip_attr_default(self, name: str, stmt: AssignmentStmt) -> bool:
|
||||
"""Controls whether to skip generating a default for an attribute."""
|
||||
return False
|
||||
|
||||
def add_method(self, fdef: FuncDef) -> None:
|
||||
handle_ext_method(self.builder, self.cdef, fdef)
|
||||
|
||||
def add_attr(self, lvalue: NameExpr, stmt: AssignmentStmt) -> None:
|
||||
# Variable declaration with no body
|
||||
if isinstance(stmt.rvalue, TempNode):
|
||||
return
|
||||
# Only treat marked class variables as class variables.
|
||||
if not (is_class_var(lvalue) or stmt.is_final_def):
|
||||
return
|
||||
typ = self.builder.load_native_type_object(self.cdef.fullname)
|
||||
value = self.builder.accept(stmt.rvalue)
|
||||
self.builder.call_c(
|
||||
py_setattr_op, [typ, self.builder.load_str(lvalue.name), value], stmt.line)
|
||||
if self.builder.non_function_scope() and stmt.is_final_def:
|
||||
self.builder.init_final_static(lvalue, value, self.cdef.name)
|
||||
|
||||
def finalize(self, ir: ClassIR) -> None:
|
||||
generate_attr_defaults(self.builder, self.cdef, self.skip_attr_default)
|
||||
create_ne_from_eq(self.builder, self.cdef)
|
||||
|
||||
|
||||
class DataClassBuilder(ExtClassBuilder):
|
||||
# controls whether an __annotations__ attribute should be added to the class
|
||||
# __dict__. This is not desirable for attrs classes where auto_attribs is
|
||||
# disabled, as attrs will reject it.
|
||||
add_annotations_to_dict = True
|
||||
|
||||
def __init__(self, builder: IRBuilder, cdef: ClassDef) -> None:
|
||||
super().__init__(builder, cdef)
|
||||
self.non_ext = self.create_non_ext_info()
|
||||
|
||||
def create_non_ext_info(self) -> NonExtClassInfo:
|
||||
"""Set up a NonExtClassInfo to track dataclass attributes.
|
||||
|
||||
In addition to setting up a normal extension class for dataclasses,
|
||||
we also collect its class attributes like a non-extension class so
|
||||
that we can hand them to the dataclass decorator.
|
||||
"""
|
||||
return NonExtClassInfo(
|
||||
self.builder.call_c(dict_new_op, [], self.cdef.line),
|
||||
self.builder.add(TupleSet([], self.cdef.line)),
|
||||
self.builder.call_c(dict_new_op, [], self.cdef.line),
|
||||
self.builder.add(LoadAddress(type_object_op.type, type_object_op.src, self.cdef.line))
|
||||
)
|
||||
|
||||
def skip_attr_default(self, name: str, stmt: AssignmentStmt) -> bool:
|
||||
return stmt.type is not None
|
||||
|
||||
def get_type_annotation(self, stmt: AssignmentStmt) -> Optional[TypeInfo]:
|
||||
# We populate __annotations__ because dataclasses uses it to determine
|
||||
# which attributes to compute on.
|
||||
ann_type = get_proper_type(stmt.type)
|
||||
if isinstance(ann_type, Instance):
|
||||
return ann_type.type
|
||||
return None
|
||||
|
||||
def add_attr(self, lvalue: NameExpr, stmt: AssignmentStmt) -> None:
|
||||
add_non_ext_class_attr_ann(self.builder, self.non_ext, lvalue, stmt,
|
||||
self.get_type_annotation)
|
||||
add_non_ext_class_attr(self.builder, self.non_ext, lvalue, stmt, self.cdef,
|
||||
self.attrs_to_cache)
|
||||
super().add_attr(lvalue, stmt)
|
||||
|
||||
def finalize(self, ir: ClassIR) -> None:
|
||||
"""Generate code to finish instantiating a dataclass.
|
||||
|
||||
This works by replacing all of the attributes on the class
|
||||
(which will be descriptors) with whatever they would be in a
|
||||
non-extension class, calling dataclass, then switching them back.
|
||||
|
||||
The resulting class is an extension class and instances of it do not
|
||||
have a __dict__ (unless something else requires it).
|
||||
All methods written explicitly in the source are compiled and
|
||||
may be called through the vtable while the methods generated
|
||||
by dataclasses are interpreted and may not be.
|
||||
|
||||
(If we just called dataclass without doing this, it would think that all
|
||||
of the descriptors for our attributes are default values and generate an
|
||||
incorrect constructor. We need to do the switch so that dataclass gets the
|
||||
appropriate defaults.)
|
||||
"""
|
||||
super().finalize(ir)
|
||||
assert self.type_obj
|
||||
add_dunders_to_non_ext_dict(self.builder, self.non_ext, self.cdef.line,
|
||||
self.add_annotations_to_dict)
|
||||
dec = self.builder.accept(
|
||||
next(d for d in self.cdef.decorators if is_dataclass_decorator(d)))
|
||||
self.builder.call_c(
|
||||
dataclass_sleight_of_hand, [dec, self.type_obj, self.non_ext.dict, self.non_ext.anns],
|
||||
self.cdef.line)
|
||||
|
||||
|
||||
class AttrsClassBuilder(DataClassBuilder):
|
||||
"""Create IR for an attrs class where auto_attribs=False (the default).
|
||||
|
||||
When auto_attribs is enabled, attrs classes behave similarly to dataclasses
|
||||
(i.e. types are stored as annotations on the class) and are thus handled
|
||||
by DataClassBuilder, but when auto_attribs is disabled the types are
|
||||
provided via attr.ib(type=...)
|
||||
"""
|
||||
|
||||
add_annotations_to_dict = False
|
||||
|
||||
def skip_attr_default(self, name: str, stmt: AssignmentStmt) -> bool:
|
||||
return True
|
||||
|
||||
def get_type_annotation(self, stmt: AssignmentStmt) -> Optional[TypeInfo]:
|
||||
if isinstance(stmt.rvalue, CallExpr):
|
||||
# find the type arg in `attr.ib(type=str)`
|
||||
callee = stmt.rvalue.callee
|
||||
if (isinstance(callee, MemberExpr) and
|
||||
callee.fullname in ['attr.ib', 'attr.attr'] and
|
||||
'type' in stmt.rvalue.arg_names):
|
||||
index = stmt.rvalue.arg_names.index('type')
|
||||
type_name = stmt.rvalue.args[index]
|
||||
if isinstance(type_name, NameExpr) and isinstance(type_name.node, TypeInfo):
|
||||
lvalue = stmt.lvalues[0]
|
||||
assert isinstance(lvalue, NameExpr)
|
||||
return type_name.node
|
||||
return None
|
||||
|
||||
|
||||
def allocate_class(builder: IRBuilder, cdef: ClassDef) -> Value:
|
||||
# OK AND NOW THE FUN PART
|
||||
base_exprs = cdef.base_type_exprs + cdef.removed_base_type_exprs
|
||||
if base_exprs:
|
||||
bases = [builder.accept(x) for x in base_exprs]
|
||||
tp_bases = builder.new_tuple(bases, cdef.line)
|
||||
else:
|
||||
tp_bases = builder.add(LoadErrorValue(object_rprimitive, is_borrowed=True))
|
||||
modname = builder.load_str(builder.module_name)
|
||||
template = builder.add(LoadStatic(object_rprimitive, cdef.name + "_template",
|
||||
builder.module_name, NAMESPACE_TYPE))
|
||||
# Create the class
|
||||
tp = builder.call_c(pytype_from_template_op,
|
||||
[template, tp_bases, modname], cdef.line)
|
||||
# Immediately fix up the trait vtables, before doing anything with the class.
|
||||
ir = builder.mapper.type_to_ir[cdef.info]
|
||||
if not ir.is_trait and not ir.builtin_base:
|
||||
builder.add(Call(
|
||||
FuncDecl(cdef.name + '_trait_vtable_setup',
|
||||
None, builder.module_name,
|
||||
FuncSignature([], bool_rprimitive)), [], -1))
|
||||
# Populate a '__mypyc_attrs__' field containing the list of attrs
|
||||
builder.call_c(py_setattr_op, [
|
||||
tp, builder.load_str('__mypyc_attrs__'),
|
||||
create_mypyc_attrs_tuple(builder, builder.mapper.type_to_ir[cdef.info], cdef.line)],
|
||||
cdef.line)
|
||||
|
||||
# Save the class
|
||||
builder.add(InitStatic(tp, cdef.name, builder.module_name, NAMESPACE_TYPE))
|
||||
|
||||
# Add it to the dict
|
||||
builder.call_c(dict_set_item_op,
|
||||
[builder.load_globals_dict(),
|
||||
builder.load_str(cdef.name),
|
||||
tp],
|
||||
cdef.line)
|
||||
|
||||
return tp
|
||||
|
||||
|
||||
# Mypy uses these internally as base classes of TypedDict classes. These are
|
||||
# lies and don't have any runtime equivalent.
|
||||
MAGIC_TYPED_DICT_CLASSES: Final[Tuple[str, ...]] = (
|
||||
'typing._TypedDict',
|
||||
'typing_extensions._TypedDict',
|
||||
)
|
||||
|
||||
|
||||
def populate_non_ext_bases(builder: IRBuilder, cdef: ClassDef) -> Value:
|
||||
"""Create base class tuple of a non-extension class.
|
||||
|
||||
The tuple is passed to the metaclass constructor.
|
||||
"""
|
||||
is_named_tuple = cdef.info.is_named_tuple
|
||||
ir = builder.mapper.type_to_ir[cdef.info]
|
||||
bases = []
|
||||
for cls in cdef.info.mro[1:]:
|
||||
if cls.fullname == 'builtins.object':
|
||||
continue
|
||||
if is_named_tuple and cls.fullname in ('typing.Sequence',
|
||||
'typing.Iterable',
|
||||
'typing.Collection',
|
||||
'typing.Reversible',
|
||||
'typing.Container'):
|
||||
# HAX: Synthesized base classes added by mypy don't exist at runtime, so skip them.
|
||||
# This could break if they were added explicitly, though...
|
||||
continue
|
||||
# Add the current class to the base classes list of concrete subclasses
|
||||
if cls in builder.mapper.type_to_ir:
|
||||
base_ir = builder.mapper.type_to_ir[cls]
|
||||
if base_ir.children is not None:
|
||||
base_ir.children.append(ir)
|
||||
|
||||
if cls.fullname in MAGIC_TYPED_DICT_CLASSES:
|
||||
# HAX: Mypy internally represents TypedDict classes differently from what
|
||||
# should happen at runtime. Replace with something that works.
|
||||
module = 'typing'
|
||||
if builder.options.capi_version < (3, 9):
|
||||
name = 'TypedDict'
|
||||
if builder.options.capi_version < (3, 8):
|
||||
# TypedDict was added to typing in Python 3.8.
|
||||
module = 'typing_extensions'
|
||||
else:
|
||||
# In Python 3.9 TypedDict is not a real type.
|
||||
name = '_TypedDict'
|
||||
base = builder.get_module_attr(module, name, cdef.line)
|
||||
elif is_named_tuple and cls.fullname == 'builtins.tuple':
|
||||
if builder.options.capi_version < (3, 9):
|
||||
name = 'NamedTuple'
|
||||
else:
|
||||
# This was changed in Python 3.9.
|
||||
name = '_NamedTuple'
|
||||
base = builder.get_module_attr('typing', name, cdef.line)
|
||||
else:
|
||||
base = builder.load_global_str(cls.name, cdef.line)
|
||||
bases.append(base)
|
||||
if cls.fullname in MAGIC_TYPED_DICT_CLASSES:
|
||||
# The remaining base classes are synthesized by mypy and should be ignored.
|
||||
break
|
||||
return builder.new_tuple(bases, cdef.line)
|
||||
|
||||
|
||||
def find_non_ext_metaclass(builder: IRBuilder, cdef: ClassDef, bases: Value) -> Value:
|
||||
"""Find the metaclass of a class from its defs and bases. """
|
||||
if cdef.metaclass:
|
||||
declared_metaclass = builder.accept(cdef.metaclass)
|
||||
else:
|
||||
if cdef.info.typeddict_type is not None and builder.options.capi_version >= (3, 9):
|
||||
# In Python 3.9, the metaclass for class-based TypedDict is typing._TypedDictMeta.
|
||||
# We can't easily calculate it generically, so special case it.
|
||||
return builder.get_module_attr('typing', '_TypedDictMeta', cdef.line)
|
||||
elif cdef.info.is_named_tuple and builder.options.capi_version >= (3, 9):
|
||||
# In Python 3.9, the metaclass for class-based NamedTuple is typing.NamedTupleMeta.
|
||||
# We can't easily calculate it generically, so special case it.
|
||||
return builder.get_module_attr('typing', 'NamedTupleMeta', cdef.line)
|
||||
|
||||
declared_metaclass = builder.add(LoadAddress(type_object_op.type,
|
||||
type_object_op.src, cdef.line))
|
||||
|
||||
return builder.call_c(py_calc_meta_op, [declared_metaclass, bases], cdef.line)
|
||||
|
||||
|
||||
def setup_non_ext_dict(builder: IRBuilder,
|
||||
cdef: ClassDef,
|
||||
metaclass: Value,
|
||||
bases: Value) -> Value:
|
||||
"""Initialize the class dictionary for a non-extension class.
|
||||
|
||||
This class dictionary is passed to the metaclass constructor.
|
||||
"""
|
||||
# Check if the metaclass defines a __prepare__ method, and if so, call it.
|
||||
has_prepare = builder.call_c(py_hasattr_op,
|
||||
[metaclass,
|
||||
builder.load_str('__prepare__')], cdef.line)
|
||||
|
||||
non_ext_dict = Register(dict_rprimitive)
|
||||
|
||||
true_block, false_block, exit_block, = BasicBlock(), BasicBlock(), BasicBlock()
|
||||
builder.add_bool_branch(has_prepare, true_block, false_block)
|
||||
|
||||
builder.activate_block(true_block)
|
||||
cls_name = builder.load_str(cdef.name)
|
||||
prepare_meth = builder.py_get_attr(metaclass, '__prepare__', cdef.line)
|
||||
prepare_dict = builder.py_call(prepare_meth, [cls_name, bases], cdef.line)
|
||||
builder.assign(non_ext_dict, prepare_dict, cdef.line)
|
||||
builder.goto(exit_block)
|
||||
|
||||
builder.activate_block(false_block)
|
||||
builder.assign(non_ext_dict, builder.call_c(dict_new_op, [], cdef.line), cdef.line)
|
||||
builder.goto(exit_block)
|
||||
builder.activate_block(exit_block)
|
||||
|
||||
return non_ext_dict
|
||||
|
||||
|
||||
def add_non_ext_class_attr_ann(builder: IRBuilder,
|
||||
non_ext: NonExtClassInfo,
|
||||
lvalue: NameExpr,
|
||||
stmt: AssignmentStmt,
|
||||
get_type_info: Optional[Callable[[AssignmentStmt],
|
||||
Optional[TypeInfo]]] = None
|
||||
) -> None:
|
||||
"""Add a class attribute to __annotations__ of a non-extension class."""
|
||||
typ: Optional[Value] = None
|
||||
if get_type_info is not None:
|
||||
type_info = get_type_info(stmt)
|
||||
if type_info:
|
||||
typ = load_type(builder, type_info, stmt.line)
|
||||
|
||||
if typ is None:
|
||||
# FIXME: if get_type_info is not provided, don't fall back to stmt.type?
|
||||
ann_type = get_proper_type(stmt.type)
|
||||
if isinstance(ann_type, Instance):
|
||||
typ = load_type(builder, ann_type.type, stmt.line)
|
||||
else:
|
||||
typ = builder.add(LoadAddress(type_object_op.type, type_object_op.src, stmt.line))
|
||||
|
||||
key = builder.load_str(lvalue.name)
|
||||
builder.call_c(dict_set_item_op, [non_ext.anns, key, typ], stmt.line)
|
||||
|
||||
|
||||
def add_non_ext_class_attr(builder: IRBuilder,
|
||||
non_ext: NonExtClassInfo,
|
||||
lvalue: NameExpr,
|
||||
stmt: AssignmentStmt,
|
||||
cdef: ClassDef,
|
||||
attr_to_cache: List[Tuple[Lvalue, RType]]) -> None:
|
||||
"""Add a class attribute to __dict__ of a non-extension class."""
|
||||
# Only add the attribute to the __dict__ if the assignment is of the form:
|
||||
# x: type = value (don't add attributes of the form 'x: type' to the __dict__).
|
||||
if not isinstance(stmt.rvalue, TempNode):
|
||||
rvalue = builder.accept(stmt.rvalue)
|
||||
builder.add_to_non_ext_dict(non_ext, lvalue.name, rvalue, stmt.line)
|
||||
# We cache enum attributes to speed up enum attribute lookup since they
|
||||
# are final.
|
||||
if (
|
||||
cdef.info.bases
|
||||
and cdef.info.bases[0].type.fullname == 'enum.Enum'
|
||||
# Skip these since Enum will remove it
|
||||
and lvalue.name not in ENUM_REMOVED_PROPS
|
||||
):
|
||||
# Enum values are always boxed, so use object_rprimitive.
|
||||
attr_to_cache.append((lvalue, object_rprimitive))
|
||||
|
||||
|
||||
def generate_attr_defaults(builder: IRBuilder, cdef: ClassDef,
|
||||
skip: Optional[Callable[[str, AssignmentStmt], bool]] = None) -> None:
|
||||
"""Generate an initialization method for default attr values (from class vars).
|
||||
|
||||
If provided, the skip arg should be a callable which will return whether
|
||||
to skip generating a default for an attribute. It will be passed the name of
|
||||
the attribute and the corresponding AssignmentStmt.
|
||||
"""
|
||||
cls = builder.mapper.type_to_ir[cdef.info]
|
||||
if cls.builtin_base:
|
||||
return
|
||||
|
||||
# Pull out all assignments in classes in the mro so we can initialize them
|
||||
# TODO: Support nested statements
|
||||
default_assignments = []
|
||||
for info in reversed(cdef.info.mro):
|
||||
if info not in builder.mapper.type_to_ir:
|
||||
continue
|
||||
for stmt in info.defn.defs.body:
|
||||
if (isinstance(stmt, AssignmentStmt)
|
||||
and isinstance(stmt.lvalues[0], NameExpr)
|
||||
and not is_class_var(stmt.lvalues[0])
|
||||
and not isinstance(stmt.rvalue, TempNode)):
|
||||
name = stmt.lvalues[0].name
|
||||
if name == '__slots__':
|
||||
continue
|
||||
|
||||
if name == '__deletable__':
|
||||
check_deletable_declaration(builder, cls, stmt.line)
|
||||
continue
|
||||
|
||||
if skip is not None and skip(name, stmt):
|
||||
continue
|
||||
|
||||
default_assignments.append(stmt)
|
||||
|
||||
if not default_assignments:
|
||||
return
|
||||
|
||||
with builder.enter_method(cls, '__mypyc_defaults_setup', bool_rprimitive):
|
||||
self_var = builder.self()
|
||||
for stmt in default_assignments:
|
||||
lvalue = stmt.lvalues[0]
|
||||
assert isinstance(lvalue, NameExpr)
|
||||
if not stmt.is_final_def and not is_constant(stmt.rvalue):
|
||||
builder.warning('Unsupported default attribute value', stmt.rvalue.line)
|
||||
|
||||
# If the attribute is initialized to None and type isn't optional,
|
||||
# don't initialize it to anything.
|
||||
attr_type = cls.attr_type(lvalue.name)
|
||||
if isinstance(stmt.rvalue, RefExpr) and stmt.rvalue.fullname == 'builtins.None':
|
||||
if (not is_optional_type(attr_type) and not is_object_rprimitive(attr_type)
|
||||
and not is_none_rprimitive(attr_type)):
|
||||
continue
|
||||
val = builder.coerce(builder.accept(stmt.rvalue), attr_type, stmt.line)
|
||||
builder.add(SetAttr(self_var, lvalue.name, val, -1))
|
||||
|
||||
builder.add(Return(builder.true()))
|
||||
|
||||
|
||||
def check_deletable_declaration(builder: IRBuilder, cl: ClassIR, line: int) -> None:
|
||||
for attr in cl.deletable:
|
||||
if attr not in cl.attributes:
|
||||
if not cl.has_attr(attr):
|
||||
builder.error('Attribute "{}" not defined'.format(attr), line)
|
||||
continue
|
||||
for base in cl.mro:
|
||||
if attr in base.property_types:
|
||||
builder.error('Cannot make property "{}" deletable'.format(attr), line)
|
||||
break
|
||||
else:
|
||||
_, base = cl.attr_details(attr)
|
||||
builder.error(('Attribute "{}" not defined in "{}" ' +
|
||||
'(defined in "{}")').format(attr, cl.name, base.name), line)
|
||||
|
||||
|
||||
def create_ne_from_eq(builder: IRBuilder, cdef: ClassDef) -> None:
|
||||
"""Create a "__ne__" method from a "__eq__" method (if only latter exists)."""
|
||||
cls = builder.mapper.type_to_ir[cdef.info]
|
||||
if cls.has_method('__eq__') and not cls.has_method('__ne__'):
|
||||
gen_glue_ne_method(builder, cls, cdef.line)
|
||||
|
||||
|
||||
def gen_glue_ne_method(builder: IRBuilder, cls: ClassIR, line: int) -> None:
|
||||
"""Generate a "__ne__" method from a "__eq__" method. """
|
||||
with builder.enter_method(cls, '__ne__', object_rprimitive):
|
||||
rhs_arg = builder.add_argument('rhs', object_rprimitive)
|
||||
|
||||
# If __eq__ returns NotImplemented, then __ne__ should also
|
||||
not_implemented_block, regular_block = BasicBlock(), BasicBlock()
|
||||
eqval = builder.add(MethodCall(builder.self(), '__eq__', [rhs_arg], line))
|
||||
not_implemented = builder.add(LoadAddress(not_implemented_op.type,
|
||||
not_implemented_op.src, line))
|
||||
builder.add(Branch(
|
||||
builder.translate_is_op(eqval, not_implemented, 'is', line),
|
||||
not_implemented_block,
|
||||
regular_block,
|
||||
Branch.BOOL))
|
||||
|
||||
builder.activate_block(regular_block)
|
||||
retval = builder.coerce(
|
||||
builder.unary_op(eqval, 'not', line), object_rprimitive, line
|
||||
)
|
||||
builder.add(Return(retval))
|
||||
|
||||
builder.activate_block(not_implemented_block)
|
||||
builder.add(Return(not_implemented))
|
||||
|
||||
|
||||
def load_non_ext_class(builder: IRBuilder,
|
||||
ir: ClassIR,
|
||||
non_ext: NonExtClassInfo,
|
||||
line: int) -> Value:
|
||||
cls_name = builder.load_str(ir.name)
|
||||
|
||||
add_dunders_to_non_ext_dict(builder, non_ext, line)
|
||||
|
||||
class_type_obj = builder.py_call(
|
||||
non_ext.metaclass,
|
||||
[cls_name, non_ext.bases, non_ext.dict],
|
||||
line
|
||||
)
|
||||
return class_type_obj
|
||||
|
||||
|
||||
def load_decorated_class(builder: IRBuilder, cdef: ClassDef, type_obj: Value) -> Value:
|
||||
"""Apply class decorators to create a decorated (non-extension) class object.
|
||||
|
||||
Given a decorated ClassDef and a register containing a
|
||||
non-extension representation of the ClassDef created via the type
|
||||
constructor, applies the corresponding decorator functions on that
|
||||
decorated ClassDef and returns a register containing the decorated
|
||||
ClassDef.
|
||||
"""
|
||||
decorators = cdef.decorators
|
||||
dec_class = type_obj
|
||||
for d in reversed(decorators):
|
||||
decorator = d.accept(builder.visitor)
|
||||
assert isinstance(decorator, Value)
|
||||
dec_class = builder.py_call(decorator, [dec_class], dec_class.line)
|
||||
return dec_class
|
||||
|
||||
|
||||
def cache_class_attrs(builder: IRBuilder,
|
||||
attrs_to_cache: List[Tuple[Lvalue, RType]],
|
||||
cdef: ClassDef) -> None:
|
||||
"""Add class attributes to be cached to the global cache."""
|
||||
typ = builder.load_native_type_object(cdef.info.fullname)
|
||||
for lval, rtype in attrs_to_cache:
|
||||
assert isinstance(lval, NameExpr)
|
||||
rval = builder.py_get_attr(typ, lval.name, cdef.line)
|
||||
builder.init_final_static(lval, rval, cdef.name, type_override=rtype)
|
||||
|
||||
|
||||
def create_mypyc_attrs_tuple(builder: IRBuilder, ir: ClassIR, line: int) -> Value:
|
||||
attrs = [name for ancestor in ir.mro for name in ancestor.attributes]
|
||||
if ir.inherits_python:
|
||||
attrs.append('__dict__')
|
||||
items = [builder.load_str(attr) for attr in attrs]
|
||||
return builder.new_tuple(items, line)
|
||||
|
||||
|
||||
def add_dunders_to_non_ext_dict(builder: IRBuilder, non_ext: NonExtClassInfo,
|
||||
line: int, add_annotations: bool = True) -> None:
|
||||
if add_annotations:
|
||||
# Add __annotations__ to the class dict.
|
||||
builder.add_to_non_ext_dict(non_ext, '__annotations__', non_ext.anns, line)
|
||||
|
||||
# We add a __doc__ attribute so if the non-extension class is decorated with the
|
||||
# dataclass decorator, dataclass will not try to look for __text_signature__.
|
||||
# https://github.com/python/cpython/blob/3.7/Lib/dataclasses.py#L957
|
||||
filler_doc_str = 'mypyc filler docstring'
|
||||
builder.add_to_non_ext_dict(
|
||||
non_ext, '__doc__', builder.load_str(filler_doc_str), line)
|
||||
builder.add_to_non_ext_dict(
|
||||
non_ext, '__module__', builder.load_str(builder.module_name), line)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,99 @@
|
|||
"""Constant folding of IR values.
|
||||
|
||||
For example, 3 + 5 can be constant folded into 8.
|
||||
"""
|
||||
|
||||
from typing import Optional, Union
|
||||
from typing_extensions import Final
|
||||
|
||||
from mypy.nodes import Expression, IntExpr, StrExpr, OpExpr, UnaryExpr, NameExpr, MemberExpr, Var
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
|
||||
|
||||
# All possible result types of constant folding
|
||||
ConstantValue = Union[int, str]
|
||||
CONST_TYPES: Final = (int, str)
|
||||
|
||||
|
||||
def constant_fold_expr(builder: IRBuilder, expr: Expression) -> Optional[ConstantValue]:
|
||||
"""Return the constant value of an expression for supported operations.
|
||||
|
||||
Return None otherwise.
|
||||
"""
|
||||
if isinstance(expr, IntExpr):
|
||||
return expr.value
|
||||
if isinstance(expr, StrExpr):
|
||||
return expr.value
|
||||
elif isinstance(expr, NameExpr):
|
||||
node = expr.node
|
||||
if isinstance(node, Var) and node.is_final:
|
||||
value = node.final_value
|
||||
if isinstance(value, (CONST_TYPES)):
|
||||
return value
|
||||
elif isinstance(expr, MemberExpr):
|
||||
final = builder.get_final_ref(expr)
|
||||
if final is not None:
|
||||
fn, final_var, native = final
|
||||
if final_var.is_final:
|
||||
value = final_var.final_value
|
||||
if isinstance(value, (CONST_TYPES)):
|
||||
return value
|
||||
elif isinstance(expr, OpExpr):
|
||||
left = constant_fold_expr(builder, expr.left)
|
||||
right = constant_fold_expr(builder, expr.right)
|
||||
if isinstance(left, int) and isinstance(right, int):
|
||||
return constant_fold_binary_int_op(expr.op, left, right)
|
||||
elif isinstance(left, str) and isinstance(right, str):
|
||||
return constant_fold_binary_str_op(expr.op, left, right)
|
||||
elif isinstance(expr, UnaryExpr):
|
||||
value = constant_fold_expr(builder, expr.expr)
|
||||
if isinstance(value, int):
|
||||
return constant_fold_unary_int_op(expr.op, value)
|
||||
return None
|
||||
|
||||
|
||||
def constant_fold_binary_int_op(op: str, left: int, right: int) -> Optional[int]:
|
||||
if op == '+':
|
||||
return left + right
|
||||
if op == '-':
|
||||
return left - right
|
||||
elif op == '*':
|
||||
return left * right
|
||||
elif op == '//':
|
||||
if right != 0:
|
||||
return left // right
|
||||
elif op == '%':
|
||||
if right != 0:
|
||||
return left % right
|
||||
elif op == '&':
|
||||
return left & right
|
||||
elif op == '|':
|
||||
return left | right
|
||||
elif op == '^':
|
||||
return left ^ right
|
||||
elif op == '<<':
|
||||
if right >= 0:
|
||||
return left << right
|
||||
elif op == '>>':
|
||||
if right >= 0:
|
||||
return left >> right
|
||||
elif op == '**':
|
||||
if right >= 0:
|
||||
return left ** right
|
||||
return None
|
||||
|
||||
|
||||
def constant_fold_unary_int_op(op: str, value: int) -> Optional[int]:
|
||||
if op == '-':
|
||||
return -value
|
||||
elif op == '~':
|
||||
return ~value
|
||||
elif op == '+':
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def constant_fold_binary_str_op(op: str, left: str, right: str) -> Optional[str]:
|
||||
if op == '+':
|
||||
return left + right
|
||||
return None
|
||||
Binary file not shown.
183
.venv/lib/python3.8/site-packages/mypyc/irbuild/context.py
Normal file
183
.venv/lib/python3.8/site-packages/mypyc/irbuild/context.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
"""Helpers that store information about functions and the related classes."""
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from mypy.nodes import FuncItem
|
||||
|
||||
from mypyc.ir.ops import Value, BasicBlock
|
||||
from mypyc.ir.func_ir import INVALID_FUNC_DEF
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.irbuild.targets import AssignmentTarget
|
||||
|
||||
|
||||
class FuncInfo:
|
||||
"""Contains information about functions as they are generated."""
|
||||
|
||||
def __init__(self,
|
||||
fitem: FuncItem = INVALID_FUNC_DEF,
|
||||
name: str = '',
|
||||
class_name: Optional[str] = None,
|
||||
namespace: str = '',
|
||||
is_nested: bool = False,
|
||||
contains_nested: bool = False,
|
||||
is_decorated: bool = False,
|
||||
in_non_ext: bool = False) -> None:
|
||||
self.fitem = fitem
|
||||
self.name = name
|
||||
self.class_name = class_name
|
||||
self.ns = namespace
|
||||
# Callable classes implement the '__call__' method, and are used to represent functions
|
||||
# that are nested inside of other functions.
|
||||
self._callable_class: Optional[ImplicitClass] = None
|
||||
# Environment classes are ClassIR instances that contain attributes representing the
|
||||
# variables in the environment of the function they correspond to. Environment classes are
|
||||
# generated for functions that contain nested functions.
|
||||
self._env_class: Optional[ClassIR] = None
|
||||
# Generator classes implement the '__next__' method, and are used to represent generators
|
||||
# returned by generator functions.
|
||||
self._generator_class: Optional[GeneratorClass] = None
|
||||
# Environment class registers are the local registers associated with instances of an
|
||||
# environment class, used for getting and setting attributes. curr_env_reg is the register
|
||||
# associated with the current environment.
|
||||
self._curr_env_reg: Optional[Value] = None
|
||||
# These are flags denoting whether a given function is nested, contains a nested function,
|
||||
# is decorated, or is within a non-extension class.
|
||||
self.is_nested = is_nested
|
||||
self.contains_nested = contains_nested
|
||||
self.is_decorated = is_decorated
|
||||
self.in_non_ext = in_non_ext
|
||||
|
||||
# TODO: add field for ret_type: RType = none_rprimitive
|
||||
|
||||
def namespaced_name(self) -> str:
|
||||
return '_'.join(x for x in [self.name, self.class_name, self.ns] if x)
|
||||
|
||||
@property
|
||||
def is_generator(self) -> bool:
|
||||
return self.fitem.is_generator or self.fitem.is_coroutine
|
||||
|
||||
@property
|
||||
def is_coroutine(self) -> bool:
|
||||
return self.fitem.is_coroutine
|
||||
|
||||
@property
|
||||
def callable_class(self) -> 'ImplicitClass':
|
||||
assert self._callable_class is not None
|
||||
return self._callable_class
|
||||
|
||||
@callable_class.setter
|
||||
def callable_class(self, cls: 'ImplicitClass') -> None:
|
||||
self._callable_class = cls
|
||||
|
||||
@property
|
||||
def env_class(self) -> ClassIR:
|
||||
assert self._env_class is not None
|
||||
return self._env_class
|
||||
|
||||
@env_class.setter
|
||||
def env_class(self, ir: ClassIR) -> None:
|
||||
self._env_class = ir
|
||||
|
||||
@property
|
||||
def generator_class(self) -> 'GeneratorClass':
|
||||
assert self._generator_class is not None
|
||||
return self._generator_class
|
||||
|
||||
@generator_class.setter
|
||||
def generator_class(self, cls: 'GeneratorClass') -> None:
|
||||
self._generator_class = cls
|
||||
|
||||
@property
|
||||
def curr_env_reg(self) -> Value:
|
||||
assert self._curr_env_reg is not None
|
||||
return self._curr_env_reg
|
||||
|
||||
|
||||
class ImplicitClass:
|
||||
"""Contains information regarding implicitly generated classes.
|
||||
|
||||
Implicit classes are generated for nested functions and generator
|
||||
functions. They are not explicitly defined in the source code.
|
||||
|
||||
NOTE: This is both a concrete class and used as a base class.
|
||||
"""
|
||||
|
||||
def __init__(self, ir: ClassIR) -> None:
|
||||
# The ClassIR instance associated with this class.
|
||||
self.ir = ir
|
||||
# The register associated with the 'self' instance for this generator class.
|
||||
self._self_reg: Optional[Value] = None
|
||||
# Environment class registers are the local registers associated with instances of an
|
||||
# environment class, used for getting and setting attributes. curr_env_reg is the register
|
||||
# associated with the current environment. prev_env_reg is the self.__mypyc_env__ field
|
||||
# associated with the previous environment.
|
||||
self._curr_env_reg: Optional[Value] = None
|
||||
self._prev_env_reg: Optional[Value] = None
|
||||
|
||||
@property
|
||||
def self_reg(self) -> Value:
|
||||
assert self._self_reg is not None
|
||||
return self._self_reg
|
||||
|
||||
@self_reg.setter
|
||||
def self_reg(self, reg: Value) -> None:
|
||||
self._self_reg = reg
|
||||
|
||||
@property
|
||||
def curr_env_reg(self) -> Value:
|
||||
assert self._curr_env_reg is not None
|
||||
return self._curr_env_reg
|
||||
|
||||
@curr_env_reg.setter
|
||||
def curr_env_reg(self, reg: Value) -> None:
|
||||
self._curr_env_reg = reg
|
||||
|
||||
@property
|
||||
def prev_env_reg(self) -> Value:
|
||||
assert self._prev_env_reg is not None
|
||||
return self._prev_env_reg
|
||||
|
||||
@prev_env_reg.setter
|
||||
def prev_env_reg(self, reg: Value) -> None:
|
||||
self._prev_env_reg = reg
|
||||
|
||||
|
||||
class GeneratorClass(ImplicitClass):
|
||||
"""Contains information about implicit generator function classes."""
|
||||
|
||||
def __init__(self, ir: ClassIR) -> None:
|
||||
super().__init__(ir)
|
||||
# This register holds the label number that the '__next__' function should go to the next
|
||||
# time it is called.
|
||||
self._next_label_reg: Optional[Value] = None
|
||||
self._next_label_target: Optional[AssignmentTarget] = None
|
||||
|
||||
# These registers hold the error values for the generator object for the case that the
|
||||
# 'throw' function is called.
|
||||
self.exc_regs: Optional[Tuple[Value, Value, Value]] = None
|
||||
|
||||
# Holds the arg passed to send
|
||||
self.send_arg_reg: Optional[Value] = None
|
||||
|
||||
# The switch block is used to decide which instruction to go using the value held in the
|
||||
# next-label register.
|
||||
self.switch_block = BasicBlock()
|
||||
self.continuation_blocks: List[BasicBlock] = []
|
||||
|
||||
@property
|
||||
def next_label_reg(self) -> Value:
|
||||
assert self._next_label_reg is not None
|
||||
return self._next_label_reg
|
||||
|
||||
@next_label_reg.setter
|
||||
def next_label_reg(self, reg: Value) -> None:
|
||||
self._next_label_reg = reg
|
||||
|
||||
@property
|
||||
def next_label_target(self) -> AssignmentTarget:
|
||||
assert self._next_label_target is not None
|
||||
return self._next_label_target
|
||||
|
||||
@next_label_target.setter
|
||||
def next_label_target(self, target: AssignmentTarget) -> None:
|
||||
self._next_label_target = target
|
||||
Binary file not shown.
207
.venv/lib/python3.8/site-packages/mypyc/irbuild/env_class.py
Normal file
207
.venv/lib/python3.8/site-packages/mypyc/irbuild/env_class.py
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
"""Generate classes representing function environments (+ related operations).
|
||||
|
||||
If we have a nested function that has non-local (free) variables, access to the
|
||||
non-locals is via an instance of an environment class. Example:
|
||||
|
||||
def f() -> int:
|
||||
x = 0 # Make 'x' an attribute of an environment class instance
|
||||
|
||||
def g() -> int:
|
||||
# We have access to the environment class instance to
|
||||
# allow accessing 'x'
|
||||
return x + 2
|
||||
|
||||
x = x + 1 # Modify the attribute
|
||||
return g()
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional, Union
|
||||
|
||||
from mypy.nodes import FuncDef, SymbolNode
|
||||
|
||||
from mypyc.common import SELF_NAME, ENV_ATTR_NAME
|
||||
from mypyc.ir.ops import Call, GetAttr, SetAttr, Value
|
||||
from mypyc.ir.rtypes import RInstance, object_rprimitive
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.irbuild.builder import IRBuilder, SymbolTarget
|
||||
from mypyc.irbuild.targets import AssignmentTargetAttr
|
||||
from mypyc.irbuild.context import FuncInfo, ImplicitClass, GeneratorClass
|
||||
|
||||
|
||||
def setup_env_class(builder: IRBuilder) -> ClassIR:
|
||||
"""Generate a class representing a function environment.
|
||||
|
||||
Note that the variables in the function environment are not
|
||||
actually populated here. This is because when the environment
|
||||
class is generated, the function environment has not yet been
|
||||
visited. This behavior is allowed so that when the compiler visits
|
||||
nested functions, it can use the returned ClassIR instance to
|
||||
figure out free variables it needs to access. The remaining
|
||||
attributes of the environment class are populated when the
|
||||
environment registers are loaded.
|
||||
|
||||
Return a ClassIR representing an environment for a function
|
||||
containing a nested function.
|
||||
"""
|
||||
env_class = ClassIR('{}_env'.format(builder.fn_info.namespaced_name()),
|
||||
builder.module_name, is_generated=True)
|
||||
env_class.attributes[SELF_NAME] = RInstance(env_class)
|
||||
if builder.fn_info.is_nested:
|
||||
# If the function is nested, its environment class must contain an environment
|
||||
# attribute pointing to its encapsulating functions' environment class.
|
||||
env_class.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_infos[-2].env_class)
|
||||
env_class.mro = [env_class]
|
||||
builder.fn_info.env_class = env_class
|
||||
builder.classes.append(env_class)
|
||||
return env_class
|
||||
|
||||
|
||||
def finalize_env_class(builder: IRBuilder) -> None:
|
||||
"""Generate, instantiate, and set up the environment of an environment class."""
|
||||
instantiate_env_class(builder)
|
||||
|
||||
# Iterate through the function arguments and replace local definitions (using registers)
|
||||
# that were previously added to the environment with references to the function's
|
||||
# environment class.
|
||||
if builder.fn_info.is_nested:
|
||||
add_args_to_env(builder, local=False, base=builder.fn_info.callable_class)
|
||||
else:
|
||||
add_args_to_env(builder, local=False, base=builder.fn_info)
|
||||
|
||||
|
||||
def instantiate_env_class(builder: IRBuilder) -> Value:
|
||||
"""Assign an environment class to a register named after the given function definition."""
|
||||
curr_env_reg = builder.add(
|
||||
Call(builder.fn_info.env_class.ctor, [], builder.fn_info.fitem.line)
|
||||
)
|
||||
|
||||
if builder.fn_info.is_nested:
|
||||
builder.fn_info.callable_class._curr_env_reg = curr_env_reg
|
||||
builder.add(SetAttr(curr_env_reg,
|
||||
ENV_ATTR_NAME,
|
||||
builder.fn_info.callable_class.prev_env_reg,
|
||||
builder.fn_info.fitem.line))
|
||||
else:
|
||||
builder.fn_info._curr_env_reg = curr_env_reg
|
||||
|
||||
return curr_env_reg
|
||||
|
||||
|
||||
def load_env_registers(builder: IRBuilder) -> None:
|
||||
"""Load the registers for the current FuncItem being visited.
|
||||
|
||||
Adds the arguments of the FuncItem to the environment. If the
|
||||
FuncItem is nested inside of another function, then this also
|
||||
loads all of the outer environments of the FuncItem into registers
|
||||
so that they can be used when accessing free variables.
|
||||
"""
|
||||
add_args_to_env(builder, local=True)
|
||||
|
||||
fn_info = builder.fn_info
|
||||
fitem = fn_info.fitem
|
||||
if fn_info.is_nested:
|
||||
load_outer_envs(builder, fn_info.callable_class)
|
||||
# If this is a FuncDef, then make sure to load the FuncDef into its own environment
|
||||
# class so that the function can be called recursively.
|
||||
if isinstance(fitem, FuncDef):
|
||||
setup_func_for_recursive_call(builder, fitem, fn_info.callable_class)
|
||||
|
||||
|
||||
def load_outer_env(builder: IRBuilder,
|
||||
base: Value,
|
||||
outer_env: Dict[SymbolNode, SymbolTarget]) -> Value:
|
||||
"""Load the environment class for a given base into a register.
|
||||
|
||||
Additionally, iterates through all of the SymbolNode and
|
||||
AssignmentTarget instances of the environment at the given index's
|
||||
symtable, and adds those instances to the environment of the
|
||||
current environment. This is done so that the current environment
|
||||
can access outer environment variables without having to reload
|
||||
all of the environment registers.
|
||||
|
||||
Returns the register where the environment class was loaded.
|
||||
"""
|
||||
env = builder.add(GetAttr(base, ENV_ATTR_NAME, builder.fn_info.fitem.line))
|
||||
assert isinstance(env.type, RInstance), '{} must be of type RInstance'.format(env)
|
||||
|
||||
for symbol, target in outer_env.items():
|
||||
env.type.class_ir.attributes[symbol.name] = target.type
|
||||
symbol_target = AssignmentTargetAttr(env, symbol.name)
|
||||
builder.add_target(symbol, symbol_target)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def load_outer_envs(builder: IRBuilder, base: ImplicitClass) -> None:
|
||||
index = len(builder.builders) - 2
|
||||
|
||||
# Load the first outer environment. This one is special because it gets saved in the
|
||||
# FuncInfo instance's prev_env_reg field.
|
||||
if index > 1:
|
||||
# outer_env = builder.fn_infos[index].environment
|
||||
outer_env = builder.symtables[index]
|
||||
if isinstance(base, GeneratorClass):
|
||||
base.prev_env_reg = load_outer_env(builder, base.curr_env_reg, outer_env)
|
||||
else:
|
||||
base.prev_env_reg = load_outer_env(builder, base.self_reg, outer_env)
|
||||
env_reg = base.prev_env_reg
|
||||
index -= 1
|
||||
|
||||
# Load the remaining outer environments into registers.
|
||||
while index > 1:
|
||||
# outer_env = builder.fn_infos[index].environment
|
||||
outer_env = builder.symtables[index]
|
||||
env_reg = load_outer_env(builder, env_reg, outer_env)
|
||||
index -= 1
|
||||
|
||||
|
||||
def add_args_to_env(builder: IRBuilder,
|
||||
local: bool = True,
|
||||
base: Optional[Union[FuncInfo, ImplicitClass]] = None,
|
||||
reassign: bool = True) -> None:
|
||||
fn_info = builder.fn_info
|
||||
if local:
|
||||
for arg in fn_info.fitem.arguments:
|
||||
rtype = builder.type_to_rtype(arg.variable.type)
|
||||
builder.add_local_reg(arg.variable, rtype, is_arg=True)
|
||||
else:
|
||||
for arg in fn_info.fitem.arguments:
|
||||
if is_free_variable(builder, arg.variable) or fn_info.is_generator:
|
||||
rtype = builder.type_to_rtype(arg.variable.type)
|
||||
assert base is not None, 'base cannot be None for adding nonlocal args'
|
||||
builder.add_var_to_env_class(arg.variable, rtype, base, reassign=reassign)
|
||||
|
||||
|
||||
def setup_func_for_recursive_call(builder: IRBuilder, fdef: FuncDef, base: ImplicitClass) -> None:
|
||||
"""Enable calling a nested function (with a callable class) recursively.
|
||||
|
||||
Adds the instance of the callable class representing the given
|
||||
FuncDef to a register in the environment so that the function can
|
||||
be called recursively. Note that this needs to be done only for
|
||||
nested functions.
|
||||
"""
|
||||
# First, set the attribute of the environment class so that GetAttr can be called on it.
|
||||
prev_env = builder.fn_infos[-2].env_class
|
||||
prev_env.attributes[fdef.name] = builder.type_to_rtype(fdef.type)
|
||||
|
||||
if isinstance(base, GeneratorClass):
|
||||
# If we are dealing with a generator class, then we need to first get the register
|
||||
# holding the current environment class, and load the previous environment class from
|
||||
# there.
|
||||
prev_env_reg = builder.add(GetAttr(base.curr_env_reg, ENV_ATTR_NAME, -1))
|
||||
else:
|
||||
prev_env_reg = base.prev_env_reg
|
||||
|
||||
# Obtain the instance of the callable class representing the FuncDef, and add it to the
|
||||
# current environment.
|
||||
val = builder.add(GetAttr(prev_env_reg, fdef.name, -1))
|
||||
target = builder.add_local_reg(fdef, object_rprimitive)
|
||||
builder.assign(target, val, -1)
|
||||
|
||||
|
||||
def is_free_variable(builder: IRBuilder, symbol: SymbolNode) -> bool:
|
||||
fitem = builder.fn_info.fitem
|
||||
return (
|
||||
fitem in builder.free_variables
|
||||
and symbol in builder.free_variables[fitem]
|
||||
)
|
||||
Binary file not shown.
808
.venv/lib/python3.8/site-packages/mypyc/irbuild/expression.py
Normal file
808
.venv/lib/python3.8/site-packages/mypyc/irbuild/expression.py
Normal file
|
|
@ -0,0 +1,808 @@
|
|||
"""Transform mypy expression ASTs to mypyc IR (Intermediate Representation).
|
||||
|
||||
The top-level AST transformation logic is implemented in mypyc.irbuild.visitor
|
||||
and mypyc.irbuild.builder.
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Union, Callable, cast
|
||||
|
||||
from mypy.nodes import (
|
||||
Expression, NameExpr, MemberExpr, SuperExpr, CallExpr, UnaryExpr, OpExpr, IndexExpr,
|
||||
ConditionalExpr, ComparisonExpr, IntExpr, FloatExpr, ComplexExpr, StrExpr,
|
||||
BytesExpr, EllipsisExpr, ListExpr, TupleExpr, DictExpr, SetExpr, ListComprehension,
|
||||
SetComprehension, DictionaryComprehension, SliceExpr, GeneratorExpr, CastExpr, StarExpr,
|
||||
AssignmentExpr,
|
||||
Var, RefExpr, MypyFile, TypeInfo, TypeApplication, LDEF, ARG_POS
|
||||
)
|
||||
from mypy.types import TupleType, Instance, TypeType, ProperType, get_proper_type
|
||||
|
||||
from mypyc.common import MAX_SHORT_INT
|
||||
from mypyc.ir.ops import (
|
||||
Value, Register, TupleGet, TupleSet, BasicBlock, Assign, LoadAddress, RaiseStandardError
|
||||
)
|
||||
from mypyc.ir.rtypes import (
|
||||
RTuple, object_rprimitive, is_none_rprimitive, int_rprimitive, is_int_rprimitive
|
||||
)
|
||||
from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD
|
||||
from mypyc.irbuild.format_str_tokenizer import (
|
||||
tokenizer_printf_style, join_formatted_strings, convert_format_expr_to_str,
|
||||
convert_format_expr_to_bytes, join_formatted_bytes
|
||||
)
|
||||
from mypyc.primitives.bytes_ops import bytes_slice_op
|
||||
from mypyc.primitives.registry import CFunctionDescription, builtin_names
|
||||
from mypyc.primitives.generic_ops import iter_op
|
||||
from mypyc.primitives.misc_ops import new_slice_op, ellipsis_op, type_op, get_module_dict_op
|
||||
from mypyc.primitives.list_ops import list_append_op, list_extend_op, list_slice_op
|
||||
from mypyc.primitives.tuple_ops import list_tuple_op, tuple_slice_op
|
||||
from mypyc.primitives.dict_ops import dict_new_op, dict_set_item_op, dict_get_item_op
|
||||
from mypyc.primitives.set_ops import set_add_op, set_update_op
|
||||
from mypyc.primitives.str_ops import str_slice_op
|
||||
from mypyc.primitives.int_ops import int_comparison_op_mapping
|
||||
from mypyc.irbuild.specialize import apply_function_specialization, apply_method_specialization
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
from mypyc.irbuild.for_helpers import (
|
||||
translate_list_comprehension, translate_set_comprehension,
|
||||
comprehension_helper
|
||||
)
|
||||
from mypyc.irbuild.constant_fold import constant_fold_expr
|
||||
|
||||
|
||||
# Name and attribute references
|
||||
|
||||
|
||||
def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value:
|
||||
if expr.node is None:
|
||||
builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR,
|
||||
"mypyc internal error: should be unreachable",
|
||||
expr.line))
|
||||
return builder.none()
|
||||
fullname = expr.node.fullname
|
||||
if fullname in builtin_names:
|
||||
typ, src = builtin_names[fullname]
|
||||
return builder.add(LoadAddress(typ, src, expr.line))
|
||||
# special cases
|
||||
if fullname == 'builtins.None':
|
||||
return builder.none()
|
||||
if fullname == 'builtins.True':
|
||||
return builder.true()
|
||||
if fullname == 'builtins.False':
|
||||
return builder.false()
|
||||
|
||||
if isinstance(expr.node, Var) and expr.node.is_final:
|
||||
value = builder.emit_load_final(
|
||||
expr.node,
|
||||
fullname,
|
||||
expr.name,
|
||||
builder.is_native_ref_expr(expr),
|
||||
builder.types[expr],
|
||||
expr.line,
|
||||
)
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports:
|
||||
return builder.load_module(expr.node.fullname)
|
||||
|
||||
# If the expression is locally defined, then read the result from the corresponding
|
||||
# assignment target and return it. Otherwise if the expression is a global, load it from
|
||||
# the globals dictionary.
|
||||
# Except for imports, that currently always happens in the global namespace.
|
||||
if expr.kind == LDEF and not (isinstance(expr.node, Var)
|
||||
and expr.node.is_suppressed_import):
|
||||
# Try to detect and error when we hit the irritating mypy bug
|
||||
# where a local variable is cast to None. (#5423)
|
||||
if (isinstance(expr.node, Var) and is_none_rprimitive(builder.node_type(expr))
|
||||
and expr.node.is_inferred):
|
||||
builder.error(
|
||||
'Local variable "{}" has inferred type None; add an annotation'.format(
|
||||
expr.node.name),
|
||||
expr.node.line)
|
||||
|
||||
# TODO: Behavior currently only defined for Var, FuncDef and MypyFile node types.
|
||||
if isinstance(expr.node, MypyFile):
|
||||
# Load reference to a module imported inside function from
|
||||
# the modules dictionary. It would be closer to Python
|
||||
# semantics to access modules imported inside functions
|
||||
# via local variables, but this is tricky since the mypy
|
||||
# AST doesn't include a Var node for the module. We
|
||||
# instead load the module separately on each access.
|
||||
mod_dict = builder.call_c(get_module_dict_op, [], expr.line)
|
||||
obj = builder.call_c(dict_get_item_op,
|
||||
[mod_dict, builder.load_str(expr.node.fullname)],
|
||||
expr.line)
|
||||
return obj
|
||||
else:
|
||||
return builder.read(builder.get_assignment_target(expr), expr.line)
|
||||
|
||||
return builder.load_global(expr)
|
||||
|
||||
|
||||
def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value:
|
||||
# First check if this is maybe a final attribute.
|
||||
final = builder.get_final_ref(expr)
|
||||
if final is not None:
|
||||
fullname, final_var, native = final
|
||||
value = builder.emit_load_final(final_var, fullname, final_var.name, native,
|
||||
builder.types[expr], expr.line)
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports:
|
||||
return builder.load_module(expr.node.fullname)
|
||||
|
||||
obj = builder.accept(expr.expr)
|
||||
rtype = builder.node_type(expr)
|
||||
# Special case: for named tuples transform attribute access to faster index access.
|
||||
typ = get_proper_type(builder.types.get(expr.expr))
|
||||
if isinstance(typ, TupleType) and typ.partial_fallback.type.is_named_tuple:
|
||||
fields = typ.partial_fallback.type.metadata['namedtuple']['fields']
|
||||
if expr.name in fields:
|
||||
index = builder.builder.load_int(fields.index(expr.name))
|
||||
return builder.gen_method_call(obj, '__getitem__', [index], rtype, expr.line)
|
||||
|
||||
check_instance_attribute_access_through_class(builder, expr, typ)
|
||||
|
||||
return builder.builder.get_attr(obj, expr.name, rtype, expr.line)
|
||||
|
||||
|
||||
def check_instance_attribute_access_through_class(builder: IRBuilder,
|
||||
expr: MemberExpr,
|
||||
typ: Optional[ProperType]) -> None:
|
||||
"""Report error if accessing an instance attribute through class object."""
|
||||
if isinstance(expr.expr, RefExpr):
|
||||
node = expr.expr.node
|
||||
if isinstance(typ, TypeType) and isinstance(typ.item, Instance):
|
||||
# TODO: Handle other item types
|
||||
node = typ.item.type
|
||||
if isinstance(node, TypeInfo):
|
||||
class_ir = builder.mapper.type_to_ir.get(node)
|
||||
if class_ir is not None and class_ir.is_ext_class:
|
||||
sym = node.get(expr.name)
|
||||
if (sym is not None
|
||||
and isinstance(sym.node, Var)
|
||||
and not sym.node.is_classvar
|
||||
and not sym.node.is_final):
|
||||
builder.error(
|
||||
'Cannot access instance attribute "{}" through class object'.format(
|
||||
expr.name),
|
||||
expr.line
|
||||
)
|
||||
builder.note(
|
||||
'(Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define '
|
||||
'a class attribute)',
|
||||
expr.line
|
||||
)
|
||||
|
||||
|
||||
def transform_super_expr(builder: IRBuilder, o: SuperExpr) -> Value:
|
||||
# warning(builder, 'can not optimize super() expression', o.line)
|
||||
sup_val = builder.load_module_attr_by_fullname('builtins.super', o.line)
|
||||
if o.call.args:
|
||||
args = [builder.accept(arg) for arg in o.call.args]
|
||||
else:
|
||||
assert o.info is not None
|
||||
typ = builder.load_native_type_object(o.info.fullname)
|
||||
ir = builder.mapper.type_to_ir[o.info]
|
||||
iter_env = iter(builder.builder.args)
|
||||
# Grab first argument
|
||||
vself: Value = next(iter_env)
|
||||
if builder.fn_info.is_generator:
|
||||
# grab sixth argument (see comment in translate_super_method_call)
|
||||
self_targ = list(builder.symtables[-1].values())[6]
|
||||
vself = builder.read(self_targ, builder.fn_info.fitem.line)
|
||||
elif not ir.is_ext_class:
|
||||
vself = next(iter_env) # second argument is self if non_extension class
|
||||
args = [typ, vself]
|
||||
res = builder.py_call(sup_val, args, o.line)
|
||||
return builder.py_get_attr(res, o.name, o.line)
|
||||
|
||||
|
||||
# Calls
|
||||
|
||||
|
||||
def transform_call_expr(builder: IRBuilder, expr: CallExpr) -> Value:
|
||||
if isinstance(expr.analyzed, CastExpr):
|
||||
return translate_cast_expr(builder, expr.analyzed)
|
||||
|
||||
callee = expr.callee
|
||||
if isinstance(callee, IndexExpr) and isinstance(callee.analyzed, TypeApplication):
|
||||
callee = callee.analyzed.expr # Unwrap type application
|
||||
|
||||
if isinstance(callee, MemberExpr):
|
||||
return apply_method_specialization(builder, expr, callee) or \
|
||||
translate_method_call(builder, expr, callee)
|
||||
elif isinstance(callee, SuperExpr):
|
||||
return translate_super_method_call(builder, expr, callee)
|
||||
else:
|
||||
return translate_call(builder, expr, callee)
|
||||
|
||||
|
||||
def translate_call(builder: IRBuilder, expr: CallExpr, callee: Expression) -> Value:
|
||||
# The common case of calls is refexprs
|
||||
if isinstance(callee, RefExpr):
|
||||
return apply_function_specialization(builder, expr, callee) or \
|
||||
translate_refexpr_call(builder, expr, callee)
|
||||
|
||||
function = builder.accept(callee)
|
||||
args = [builder.accept(arg) for arg in expr.args]
|
||||
return builder.py_call(function, args, expr.line,
|
||||
arg_kinds=expr.arg_kinds, arg_names=expr.arg_names)
|
||||
|
||||
|
||||
def translate_refexpr_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value:
|
||||
"""Translate a non-method call."""
|
||||
# Gen the argument values
|
||||
arg_values = [builder.accept(arg) for arg in expr.args]
|
||||
|
||||
return builder.call_refexpr_with_args(expr, callee, arg_values)
|
||||
|
||||
|
||||
def translate_method_call(builder: IRBuilder, expr: CallExpr, callee: MemberExpr) -> Value:
|
||||
"""Generate IR for an arbitrary call of form e.m(...).
|
||||
|
||||
This can also deal with calls to module-level functions.
|
||||
"""
|
||||
if builder.is_native_ref_expr(callee):
|
||||
# Call to module-level native function or such
|
||||
return translate_call(builder, expr, callee)
|
||||
elif (
|
||||
isinstance(callee.expr, RefExpr)
|
||||
and isinstance(callee.expr.node, TypeInfo)
|
||||
and callee.expr.node in builder.mapper.type_to_ir
|
||||
and builder.mapper.type_to_ir[callee.expr.node].has_method(callee.name)
|
||||
):
|
||||
# Call a method via the *class*
|
||||
assert isinstance(callee.expr.node, TypeInfo)
|
||||
ir = builder.mapper.type_to_ir[callee.expr.node]
|
||||
decl = ir.method_decl(callee.name)
|
||||
args = []
|
||||
arg_kinds, arg_names = expr.arg_kinds[:], expr.arg_names[:]
|
||||
# Add the class argument for class methods in extension classes
|
||||
if decl.kind == FUNC_CLASSMETHOD and ir.is_ext_class:
|
||||
args.append(builder.load_native_type_object(callee.expr.node.fullname))
|
||||
arg_kinds.insert(0, ARG_POS)
|
||||
arg_names.insert(0, None)
|
||||
args += [builder.accept(arg) for arg in expr.args]
|
||||
|
||||
if ir.is_ext_class:
|
||||
return builder.builder.call(decl, args, arg_kinds, arg_names, expr.line)
|
||||
else:
|
||||
obj = builder.accept(callee.expr)
|
||||
return builder.gen_method_call(obj,
|
||||
callee.name,
|
||||
args,
|
||||
builder.node_type(expr),
|
||||
expr.line,
|
||||
expr.arg_kinds,
|
||||
expr.arg_names)
|
||||
|
||||
elif builder.is_module_member_expr(callee):
|
||||
# Fall back to a PyCall for non-native module calls
|
||||
function = builder.accept(callee)
|
||||
args = [builder.accept(arg) for arg in expr.args]
|
||||
return builder.py_call(function, args, expr.line,
|
||||
arg_kinds=expr.arg_kinds, arg_names=expr.arg_names)
|
||||
else:
|
||||
receiver_typ = builder.node_type(callee.expr)
|
||||
|
||||
# If there is a specializer for this method name/type, try calling it.
|
||||
# We would return the first successful one.
|
||||
val = apply_method_specialization(builder, expr, callee, receiver_typ)
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
obj = builder.accept(callee.expr)
|
||||
args = [builder.accept(arg) for arg in expr.args]
|
||||
return builder.gen_method_call(obj,
|
||||
callee.name,
|
||||
args,
|
||||
builder.node_type(expr),
|
||||
expr.line,
|
||||
expr.arg_kinds,
|
||||
expr.arg_names)
|
||||
|
||||
|
||||
def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: SuperExpr) -> Value:
|
||||
if callee.info is None or (len(callee.call.args) != 0 and len(callee.call.args) != 2):
|
||||
return translate_call(builder, expr, callee)
|
||||
|
||||
# We support two-argument super but only when it is super(CurrentClass, self)
|
||||
# TODO: We could support it when it is a parent class in many cases?
|
||||
if len(callee.call.args) == 2:
|
||||
self_arg = callee.call.args[1]
|
||||
if (
|
||||
not isinstance(self_arg, NameExpr)
|
||||
or not isinstance(self_arg.node, Var)
|
||||
or not self_arg.node.is_self
|
||||
):
|
||||
return translate_call(builder, expr, callee)
|
||||
|
||||
typ_arg = callee.call.args[0]
|
||||
if (
|
||||
not isinstance(typ_arg, NameExpr)
|
||||
or not isinstance(typ_arg.node, TypeInfo)
|
||||
or callee.info is not typ_arg.node
|
||||
):
|
||||
return translate_call(builder, expr, callee)
|
||||
|
||||
ir = builder.mapper.type_to_ir[callee.info]
|
||||
# Search for the method in the mro, skipping ourselves. We
|
||||
# determine targets of super calls to native methods statically.
|
||||
for base in ir.mro[1:]:
|
||||
if callee.name in base.method_decls:
|
||||
break
|
||||
else:
|
||||
if (ir.is_ext_class
|
||||
and ir.builtin_base is None
|
||||
and not ir.inherits_python
|
||||
and callee.name == '__init__'
|
||||
and len(expr.args) == 0):
|
||||
# Call translates to object.__init__(self), which is a
|
||||
# no-op, so omit the call.
|
||||
return builder.none()
|
||||
return translate_call(builder, expr, callee)
|
||||
|
||||
decl = base.method_decl(callee.name)
|
||||
arg_values = [builder.accept(arg) for arg in expr.args]
|
||||
arg_kinds, arg_names = expr.arg_kinds[:], expr.arg_names[:]
|
||||
|
||||
if decl.kind != FUNC_STATICMETHOD:
|
||||
# Grab first argument
|
||||
vself: Value = builder.self()
|
||||
if decl.kind == FUNC_CLASSMETHOD:
|
||||
vself = builder.call_c(type_op, [vself], expr.line)
|
||||
elif builder.fn_info.is_generator:
|
||||
# For generator classes, the self target is the 6th value
|
||||
# in the symbol table (which is an ordered dict). This is sort
|
||||
# of ugly, but we can't search by name since the 'self' parameter
|
||||
# could be named anything, and it doesn't get added to the
|
||||
# environment indexes.
|
||||
self_targ = list(builder.symtables[-1].values())[6]
|
||||
vself = builder.read(self_targ, builder.fn_info.fitem.line)
|
||||
arg_values.insert(0, vself)
|
||||
arg_kinds.insert(0, ARG_POS)
|
||||
arg_names.insert(0, None)
|
||||
|
||||
return builder.builder.call(decl, arg_values, arg_kinds, arg_names, expr.line)
|
||||
|
||||
|
||||
def translate_cast_expr(builder: IRBuilder, expr: CastExpr) -> Value:
|
||||
src = builder.accept(expr.expr)
|
||||
target_type = builder.type_to_rtype(expr.type)
|
||||
return builder.coerce(src, target_type, expr.line)
|
||||
|
||||
|
||||
# Operators
|
||||
|
||||
|
||||
def transform_unary_expr(builder: IRBuilder, expr: UnaryExpr) -> Value:
|
||||
folded = try_constant_fold(builder, expr)
|
||||
if folded:
|
||||
return folded
|
||||
|
||||
return builder.unary_op(builder.accept(expr.expr), expr.op, expr.line)
|
||||
|
||||
|
||||
def transform_op_expr(builder: IRBuilder, expr: OpExpr) -> Value:
|
||||
if expr.op in ('and', 'or'):
|
||||
return builder.shortcircuit_expr(expr)
|
||||
|
||||
# Special case for string formatting
|
||||
if expr.op == '%' and (isinstance(expr.left, StrExpr) or isinstance(expr.left, BytesExpr)):
|
||||
ret = translate_printf_style_formatting(builder, expr.left, expr.right)
|
||||
if ret is not None:
|
||||
return ret
|
||||
|
||||
folded = try_constant_fold(builder, expr)
|
||||
if folded:
|
||||
return folded
|
||||
|
||||
return builder.binary_op(
|
||||
builder.accept(expr.left), builder.accept(expr.right), expr.op, expr.line
|
||||
)
|
||||
|
||||
|
||||
def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value:
|
||||
base = builder.accept(expr.base)
|
||||
index = expr.index
|
||||
|
||||
if isinstance(base.type, RTuple) and isinstance(index, IntExpr):
|
||||
return builder.add(TupleGet(base, index.value, expr.line))
|
||||
|
||||
if isinstance(index, SliceExpr):
|
||||
value = try_gen_slice_op(builder, base, index)
|
||||
if value:
|
||||
return value
|
||||
|
||||
index_reg = builder.accept(expr.index)
|
||||
return builder.gen_method_call(
|
||||
base, '__getitem__', [index_reg], builder.node_type(expr), expr.line)
|
||||
|
||||
|
||||
def try_constant_fold(builder: IRBuilder, expr: Expression) -> Optional[Value]:
|
||||
"""Return the constant value of an expression if possible.
|
||||
|
||||
Return None otherwise.
|
||||
"""
|
||||
value = constant_fold_expr(builder, expr)
|
||||
if isinstance(value, int):
|
||||
return builder.load_int(value)
|
||||
elif isinstance(value, str):
|
||||
return builder.load_str(value)
|
||||
return None
|
||||
|
||||
|
||||
def try_gen_slice_op(builder: IRBuilder, base: Value, index: SliceExpr) -> Optional[Value]:
|
||||
"""Generate specialized slice op for some index expressions.
|
||||
|
||||
Return None if a specialized op isn't available.
|
||||
|
||||
This supports obj[x:y], obj[:x], and obj[x:] for a few types.
|
||||
"""
|
||||
if index.stride:
|
||||
# We can only handle the default stride of 1.
|
||||
return None
|
||||
|
||||
if index.begin_index:
|
||||
begin_type = builder.node_type(index.begin_index)
|
||||
else:
|
||||
begin_type = int_rprimitive
|
||||
if index.end_index:
|
||||
end_type = builder.node_type(index.end_index)
|
||||
else:
|
||||
end_type = int_rprimitive
|
||||
|
||||
# Both begin and end index must be int (or missing).
|
||||
if is_int_rprimitive(begin_type) and is_int_rprimitive(end_type):
|
||||
if index.begin_index:
|
||||
begin = builder.accept(index.begin_index)
|
||||
else:
|
||||
begin = builder.load_int(0)
|
||||
if index.end_index:
|
||||
end = builder.accept(index.end_index)
|
||||
else:
|
||||
# Replace missing end index with the largest short integer
|
||||
# (a sequence can't be longer).
|
||||
end = builder.load_int(MAX_SHORT_INT)
|
||||
candidates = [list_slice_op, tuple_slice_op, str_slice_op, bytes_slice_op]
|
||||
return builder.builder.matching_call_c(candidates, [base, begin, end], index.line)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Value:
|
||||
if_body, else_body, next_block = BasicBlock(), BasicBlock(), BasicBlock()
|
||||
|
||||
builder.process_conditional(expr.cond, if_body, else_body)
|
||||
expr_type = builder.node_type(expr)
|
||||
# Having actual Phi nodes would be really nice here!
|
||||
target = Register(expr_type)
|
||||
|
||||
builder.activate_block(if_body)
|
||||
true_value = builder.accept(expr.if_expr)
|
||||
true_value = builder.coerce(true_value, expr_type, expr.line)
|
||||
builder.add(Assign(target, true_value))
|
||||
builder.goto(next_block)
|
||||
|
||||
builder.activate_block(else_body)
|
||||
false_value = builder.accept(expr.else_expr)
|
||||
false_value = builder.coerce(false_value, expr_type, expr.line)
|
||||
builder.add(Assign(target, false_value))
|
||||
builder.goto(next_block)
|
||||
|
||||
builder.activate_block(next_block)
|
||||
|
||||
return target
|
||||
|
||||
|
||||
def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value:
|
||||
# x in (...)/[...]
|
||||
# x not in (...)/[...]
|
||||
if (e.operators[0] in ['in', 'not in']
|
||||
and len(e.operators) == 1
|
||||
and isinstance(e.operands[1], (TupleExpr, ListExpr))):
|
||||
items = e.operands[1].items
|
||||
n_items = len(items)
|
||||
# x in y -> x == y[0] or ... or x == y[n]
|
||||
# x not in y -> x != y[0] and ... and x != y[n]
|
||||
# 16 is arbitrarily chosen to limit code size
|
||||
if 1 < n_items < 16:
|
||||
if e.operators[0] == 'in':
|
||||
bin_op = 'or'
|
||||
cmp_op = '=='
|
||||
else:
|
||||
bin_op = 'and'
|
||||
cmp_op = '!='
|
||||
lhs = e.operands[0]
|
||||
mypy_file = builder.graph['builtins'].tree
|
||||
assert mypy_file is not None
|
||||
bool_type = Instance(cast(TypeInfo, mypy_file.names['bool'].node), [])
|
||||
exprs = []
|
||||
for item in items:
|
||||
expr = ComparisonExpr([cmp_op], [lhs, item])
|
||||
builder.types[expr] = bool_type
|
||||
exprs.append(expr)
|
||||
|
||||
or_expr: Expression = exprs.pop(0)
|
||||
for expr in exprs:
|
||||
or_expr = OpExpr(bin_op, or_expr, expr)
|
||||
builder.types[or_expr] = bool_type
|
||||
return builder.accept(or_expr)
|
||||
# x in [y]/(y) -> x == y
|
||||
# x not in [y]/(y) -> x != y
|
||||
elif n_items == 1:
|
||||
if e.operators[0] == 'in':
|
||||
cmp_op = '=='
|
||||
else:
|
||||
cmp_op = '!='
|
||||
e.operators = [cmp_op]
|
||||
e.operands[1] = items[0]
|
||||
# x in []/() -> False
|
||||
# x not in []/() -> True
|
||||
elif n_items == 0:
|
||||
if e.operators[0] == 'in':
|
||||
return builder.false()
|
||||
else:
|
||||
return builder.true()
|
||||
|
||||
# TODO: Don't produce an expression when used in conditional context
|
||||
# All of the trickiness here is due to support for chained conditionals
|
||||
# (`e1 < e2 > e3`, etc). `e1 < e2 > e3` is approximately equivalent to
|
||||
# `e1 < e2 and e2 > e3` except that `e2` is only evaluated once.
|
||||
expr_type = builder.node_type(e)
|
||||
|
||||
# go(i, prev) generates code for `ei opi e{i+1} op{i+1} ... en`,
|
||||
# assuming that prev contains the value of `ei`.
|
||||
def go(i: int, prev: Value) -> Value:
|
||||
if i == len(e.operators) - 1:
|
||||
return transform_basic_comparison(
|
||||
builder, e.operators[i], prev, builder.accept(e.operands[i + 1]), e.line)
|
||||
|
||||
next = builder.accept(e.operands[i + 1])
|
||||
return builder.builder.shortcircuit_helper(
|
||||
'and', expr_type,
|
||||
lambda: transform_basic_comparison(
|
||||
builder, e.operators[i], prev, next, e.line),
|
||||
lambda: go(i + 1, next),
|
||||
e.line)
|
||||
|
||||
return go(0, builder.accept(e.operands[0]))
|
||||
|
||||
|
||||
def transform_basic_comparison(builder: IRBuilder,
|
||||
op: str,
|
||||
left: Value,
|
||||
right: Value,
|
||||
line: int) -> Value:
|
||||
if (is_int_rprimitive(left.type) and is_int_rprimitive(right.type)
|
||||
and op in int_comparison_op_mapping.keys()):
|
||||
return builder.compare_tagged(left, right, op, line)
|
||||
negate = False
|
||||
if op == 'is not':
|
||||
op, negate = 'is', True
|
||||
elif op == 'not in':
|
||||
op, negate = 'in', True
|
||||
|
||||
target = builder.binary_op(left, right, op, line)
|
||||
|
||||
if negate:
|
||||
target = builder.unary_op(target, 'not', line)
|
||||
return target
|
||||
|
||||
|
||||
def translate_printf_style_formatting(builder: IRBuilder,
|
||||
format_expr: Union[StrExpr, BytesExpr],
|
||||
rhs: Expression) -> Optional[Value]:
|
||||
tokens = tokenizer_printf_style(format_expr.value)
|
||||
if tokens is not None:
|
||||
literals, format_ops = tokens
|
||||
|
||||
exprs = []
|
||||
if isinstance(rhs, TupleExpr):
|
||||
exprs = rhs.items
|
||||
elif isinstance(rhs, Expression):
|
||||
exprs.append(rhs)
|
||||
|
||||
if isinstance(format_expr, BytesExpr):
|
||||
substitutions = convert_format_expr_to_bytes(builder, format_ops,
|
||||
exprs, format_expr.line)
|
||||
if substitutions is not None:
|
||||
return join_formatted_bytes(builder, literals, substitutions, format_expr.line)
|
||||
else:
|
||||
substitutions = convert_format_expr_to_str(builder, format_ops,
|
||||
exprs, format_expr.line)
|
||||
if substitutions is not None:
|
||||
return join_formatted_strings(builder, literals, substitutions, format_expr.line)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# Literals
|
||||
|
||||
|
||||
def transform_int_expr(builder: IRBuilder, expr: IntExpr) -> Value:
|
||||
return builder.builder.load_int(expr.value)
|
||||
|
||||
|
||||
def transform_float_expr(builder: IRBuilder, expr: FloatExpr) -> Value:
|
||||
return builder.builder.load_float(expr.value)
|
||||
|
||||
|
||||
def transform_complex_expr(builder: IRBuilder, expr: ComplexExpr) -> Value:
|
||||
return builder.builder.load_complex(expr.value)
|
||||
|
||||
|
||||
def transform_str_expr(builder: IRBuilder, expr: StrExpr) -> Value:
|
||||
return builder.load_str(expr.value)
|
||||
|
||||
|
||||
def transform_bytes_expr(builder: IRBuilder, expr: BytesExpr) -> Value:
|
||||
return builder.load_bytes_from_str_literal(expr.value)
|
||||
|
||||
|
||||
def transform_ellipsis(builder: IRBuilder, o: EllipsisExpr) -> Value:
|
||||
return builder.add(LoadAddress(ellipsis_op.type, ellipsis_op.src, o.line))
|
||||
|
||||
|
||||
# Display expressions
|
||||
|
||||
|
||||
def transform_list_expr(builder: IRBuilder, expr: ListExpr) -> Value:
|
||||
return _visit_list_display(builder, expr.items, expr.line)
|
||||
|
||||
|
||||
def _visit_list_display(builder: IRBuilder, items: List[Expression], line: int) -> Value:
|
||||
return _visit_display(
|
||||
builder,
|
||||
items,
|
||||
builder.new_list_op,
|
||||
list_append_op,
|
||||
list_extend_op,
|
||||
line,
|
||||
True
|
||||
)
|
||||
|
||||
|
||||
def transform_tuple_expr(builder: IRBuilder, expr: TupleExpr) -> Value:
|
||||
if any(isinstance(item, StarExpr) for item in expr.items):
|
||||
# create a tuple of unknown length
|
||||
return _visit_tuple_display(builder, expr)
|
||||
|
||||
# create a tuple of fixed length (RTuple)
|
||||
tuple_type = builder.node_type(expr)
|
||||
# When handling NamedTuple et. al we might not have proper type info,
|
||||
# so make some up if we need it.
|
||||
types = (tuple_type.types if isinstance(tuple_type, RTuple)
|
||||
else [object_rprimitive] * len(expr.items))
|
||||
|
||||
items = []
|
||||
for item_expr, item_type in zip(expr.items, types):
|
||||
reg = builder.accept(item_expr)
|
||||
items.append(builder.coerce(reg, item_type, item_expr.line))
|
||||
return builder.add(TupleSet(items, expr.line))
|
||||
|
||||
|
||||
def _visit_tuple_display(builder: IRBuilder, expr: TupleExpr) -> Value:
|
||||
"""Create a list, then turn it into a tuple."""
|
||||
val_as_list = _visit_list_display(builder, expr.items, expr.line)
|
||||
return builder.call_c(list_tuple_op, [val_as_list], expr.line)
|
||||
|
||||
|
||||
def transform_dict_expr(builder: IRBuilder, expr: DictExpr) -> Value:
|
||||
"""First accepts all keys and values, then makes a dict out of them."""
|
||||
key_value_pairs = []
|
||||
for key_expr, value_expr in expr.items:
|
||||
key = builder.accept(key_expr) if key_expr is not None else None
|
||||
value = builder.accept(value_expr)
|
||||
key_value_pairs.append((key, value))
|
||||
|
||||
return builder.builder.make_dict(key_value_pairs, expr.line)
|
||||
|
||||
|
||||
def transform_set_expr(builder: IRBuilder, expr: SetExpr) -> Value:
|
||||
return _visit_display(
|
||||
builder,
|
||||
expr.items,
|
||||
builder.new_set_op,
|
||||
set_add_op,
|
||||
set_update_op,
|
||||
expr.line,
|
||||
False
|
||||
)
|
||||
|
||||
|
||||
def _visit_display(builder: IRBuilder,
|
||||
items: List[Expression],
|
||||
constructor_op: Callable[[List[Value], int], Value],
|
||||
append_op: CFunctionDescription,
|
||||
extend_op: CFunctionDescription,
|
||||
line: int,
|
||||
is_list: bool
|
||||
) -> Value:
|
||||
accepted_items = []
|
||||
for item in items:
|
||||
if isinstance(item, StarExpr):
|
||||
accepted_items.append((True, builder.accept(item.expr)))
|
||||
else:
|
||||
accepted_items.append((False, builder.accept(item)))
|
||||
|
||||
result: Union[Value, None] = None
|
||||
initial_items = []
|
||||
for starred, value in accepted_items:
|
||||
if result is None and not starred and is_list:
|
||||
initial_items.append(value)
|
||||
continue
|
||||
|
||||
if result is None:
|
||||
result = constructor_op(initial_items, line)
|
||||
|
||||
builder.call_c(extend_op if starred else append_op, [result, value], line)
|
||||
|
||||
if result is None:
|
||||
result = constructor_op(initial_items, line)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# Comprehensions
|
||||
|
||||
|
||||
def transform_list_comprehension(builder: IRBuilder, o: ListComprehension) -> Value:
|
||||
if any(o.generator.is_async):
|
||||
builder.error('async comprehensions are unimplemented', o.line)
|
||||
return translate_list_comprehension(builder, o.generator)
|
||||
|
||||
|
||||
def transform_set_comprehension(builder: IRBuilder, o: SetComprehension) -> Value:
|
||||
if any(o.generator.is_async):
|
||||
builder.error('async comprehensions are unimplemented', o.line)
|
||||
return translate_set_comprehension(builder, o.generator)
|
||||
|
||||
|
||||
def transform_dictionary_comprehension(builder: IRBuilder, o: DictionaryComprehension) -> Value:
|
||||
if any(o.is_async):
|
||||
builder.error('async comprehensions are unimplemented', o.line)
|
||||
|
||||
d = builder.call_c(dict_new_op, [], o.line)
|
||||
loop_params = list(zip(o.indices, o.sequences, o.condlists))
|
||||
|
||||
def gen_inner_stmts() -> None:
|
||||
k = builder.accept(o.key)
|
||||
v = builder.accept(o.value)
|
||||
builder.call_c(dict_set_item_op, [d, k, v], o.line)
|
||||
|
||||
comprehension_helper(builder, loop_params, gen_inner_stmts, o.line)
|
||||
return d
|
||||
|
||||
|
||||
# Misc
|
||||
|
||||
|
||||
def transform_slice_expr(builder: IRBuilder, expr: SliceExpr) -> Value:
|
||||
def get_arg(arg: Optional[Expression]) -> Value:
|
||||
if arg is None:
|
||||
return builder.none_object()
|
||||
else:
|
||||
return builder.accept(arg)
|
||||
|
||||
args = [get_arg(expr.begin_index),
|
||||
get_arg(expr.end_index),
|
||||
get_arg(expr.stride)]
|
||||
return builder.call_c(new_slice_op, args, expr.line)
|
||||
|
||||
|
||||
def transform_generator_expr(builder: IRBuilder, o: GeneratorExpr) -> Value:
|
||||
if any(o.is_async):
|
||||
builder.error('async comprehensions are unimplemented', o.line)
|
||||
|
||||
builder.warning('Treating generator comprehension as list', o.line)
|
||||
return builder.call_c(
|
||||
iter_op, [translate_list_comprehension(builder, o)], o.line
|
||||
)
|
||||
|
||||
|
||||
def transform_assignment_expr(builder: IRBuilder, o: AssignmentExpr) -> Value:
|
||||
value = builder.accept(o.value)
|
||||
target = builder.get_assignment_target(o.target)
|
||||
builder.assign(target, value, o.line)
|
||||
return value
|
||||
Binary file not shown.
880
.venv/lib/python3.8/site-packages/mypyc/irbuild/for_helpers.py
Normal file
880
.venv/lib/python3.8/site-packages/mypyc/irbuild/for_helpers.py
Normal file
|
|
@ -0,0 +1,880 @@
|
|||
"""Helpers for generating for loops and comprehensions.
|
||||
|
||||
We special case certain kinds for loops such as "for x in range(...)"
|
||||
for better efficiency. Each for loop generator class below deals one
|
||||
such special case.
|
||||
"""
|
||||
|
||||
from typing import Union, List, Optional, Tuple, Callable
|
||||
from typing_extensions import Type, ClassVar
|
||||
|
||||
from mypy.nodes import (
|
||||
Lvalue, Expression, TupleExpr, CallExpr, RefExpr, GeneratorExpr, ARG_POS, MemberExpr, TypeAlias
|
||||
)
|
||||
from mypyc.ir.ops import (
|
||||
Value, BasicBlock, Integer, Branch, Register, TupleGet, TupleSet, IntOp
|
||||
)
|
||||
from mypyc.ir.rtypes import (
|
||||
RType, is_short_int_rprimitive, is_list_rprimitive, is_sequence_rprimitive,
|
||||
is_tuple_rprimitive, is_dict_rprimitive, is_str_rprimitive,
|
||||
RTuple, short_int_rprimitive, int_rprimitive
|
||||
)
|
||||
from mypyc.primitives.registry import CFunctionDescription
|
||||
from mypyc.primitives.dict_ops import (
|
||||
dict_next_key_op, dict_next_value_op, dict_next_item_op, dict_check_size_op,
|
||||
dict_key_iter_op, dict_value_iter_op, dict_item_iter_op
|
||||
)
|
||||
from mypyc.primitives.list_ops import list_append_op, list_get_item_unsafe_op, new_list_set_item_op
|
||||
from mypyc.primitives.set_ops import set_add_op
|
||||
from mypyc.primitives.generic_ops import iter_op, next_op
|
||||
from mypyc.primitives.exc_ops import no_err_occurred_op
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
from mypyc.irbuild.targets import AssignmentTarget, AssignmentTargetTuple
|
||||
|
||||
GenFunc = Callable[[], None]
|
||||
|
||||
|
||||
def for_loop_helper(builder: IRBuilder, index: Lvalue, expr: Expression,
|
||||
body_insts: GenFunc, else_insts: Optional[GenFunc],
|
||||
line: int) -> None:
|
||||
"""Generate IR for a loop.
|
||||
|
||||
Args:
|
||||
index: the loop index Lvalue
|
||||
expr: the expression to iterate over
|
||||
body_insts: a function that generates the body of the loop
|
||||
else_insts: a function that generates the else block instructions
|
||||
"""
|
||||
# Body of the loop
|
||||
body_block = BasicBlock()
|
||||
# Block that steps to the next item
|
||||
step_block = BasicBlock()
|
||||
# Block for the else clause, if we need it
|
||||
else_block = BasicBlock()
|
||||
# Block executed after the loop
|
||||
exit_block = BasicBlock()
|
||||
|
||||
# Determine where we want to exit, if our condition check fails.
|
||||
normal_loop_exit = else_block if else_insts is not None else exit_block
|
||||
|
||||
for_gen = make_for_loop_generator(builder, index, expr, body_block, normal_loop_exit, line)
|
||||
|
||||
builder.push_loop_stack(step_block, exit_block)
|
||||
condition_block = BasicBlock()
|
||||
builder.goto_and_activate(condition_block)
|
||||
|
||||
# Add loop condition check.
|
||||
for_gen.gen_condition()
|
||||
|
||||
# Generate loop body.
|
||||
builder.activate_block(body_block)
|
||||
for_gen.begin_body()
|
||||
body_insts()
|
||||
|
||||
# We generate a separate step block (which might be empty).
|
||||
builder.goto_and_activate(step_block)
|
||||
for_gen.gen_step()
|
||||
# Go back to loop condition.
|
||||
builder.goto(condition_block)
|
||||
|
||||
for_gen.add_cleanup(normal_loop_exit)
|
||||
builder.pop_loop_stack()
|
||||
|
||||
if else_insts is not None:
|
||||
builder.activate_block(else_block)
|
||||
else_insts()
|
||||
builder.goto(exit_block)
|
||||
|
||||
builder.activate_block(exit_block)
|
||||
|
||||
|
||||
def for_loop_helper_with_index(builder: IRBuilder,
|
||||
index: Lvalue,
|
||||
expr: Expression,
|
||||
expr_reg: Value,
|
||||
body_insts: Callable[[Value], None], line: int) -> None:
|
||||
"""Generate IR for a sequence iteration.
|
||||
|
||||
This function only works for sequence type. Compared to for_loop_helper,
|
||||
it would feed iteration index to body_insts.
|
||||
|
||||
Args:
|
||||
index: the loop index Lvalue
|
||||
expr: the expression to iterate over
|
||||
body_insts: a function that generates the body of the loop.
|
||||
It needs a index as parameter.
|
||||
"""
|
||||
assert is_sequence_rprimitive(expr_reg.type)
|
||||
target_type = builder.get_sequence_type(expr)
|
||||
|
||||
body_block = BasicBlock()
|
||||
step_block = BasicBlock()
|
||||
exit_block = BasicBlock()
|
||||
condition_block = BasicBlock()
|
||||
|
||||
for_gen = ForSequence(builder, index, body_block, exit_block, line, False)
|
||||
for_gen.init(expr_reg, target_type, reverse=False)
|
||||
|
||||
builder.push_loop_stack(step_block, exit_block)
|
||||
|
||||
builder.goto_and_activate(condition_block)
|
||||
for_gen.gen_condition()
|
||||
|
||||
builder.activate_block(body_block)
|
||||
for_gen.begin_body()
|
||||
body_insts(builder.read(for_gen.index_target))
|
||||
|
||||
builder.goto_and_activate(step_block)
|
||||
for_gen.gen_step()
|
||||
builder.goto(condition_block)
|
||||
|
||||
for_gen.add_cleanup(exit_block)
|
||||
builder.pop_loop_stack()
|
||||
|
||||
builder.activate_block(exit_block)
|
||||
|
||||
|
||||
def sequence_from_generator_preallocate_helper(
|
||||
builder: IRBuilder,
|
||||
gen: GeneratorExpr,
|
||||
empty_op_llbuilder: Callable[[Value, int], Value],
|
||||
set_item_op: CFunctionDescription) -> Optional[Value]:
|
||||
"""Generate a new tuple or list from a simple generator expression.
|
||||
|
||||
Currently we only optimize for simplest generator expression, which means that
|
||||
there is no condition list in the generator and only one original sequence with
|
||||
one index is allowed.
|
||||
|
||||
e.g. (1) tuple(f(x) for x in a_list/a_tuple)
|
||||
(2) list(f(x) for x in a_list/a_tuple)
|
||||
(3) [f(x) for x in a_list/a_tuple]
|
||||
RTuple as an original sequence is not supported yet.
|
||||
|
||||
Args:
|
||||
empty_op_llbuilder: A function that can generate an empty sequence op when
|
||||
passed in length. See `new_list_op_with_length` and `new_tuple_op_with_length`
|
||||
for detailed implementation.
|
||||
set_item_op: A primitive that can modify an arbitrary position of a sequence.
|
||||
The op should have three arguments:
|
||||
- Self
|
||||
- Target position
|
||||
- New Value
|
||||
See `new_list_set_item_op` and `new_tuple_set_item_op` for detailed
|
||||
implementation.
|
||||
"""
|
||||
if len(gen.sequences) == 1 and len(gen.indices) == 1 and len(gen.condlists[0]) == 0:
|
||||
rtype = builder.node_type(gen.sequences[0])
|
||||
if (is_list_rprimitive(rtype) or is_tuple_rprimitive(rtype)
|
||||
or is_str_rprimitive(rtype)):
|
||||
sequence = builder.accept(gen.sequences[0])
|
||||
length = builder.builder.builtin_len(sequence, gen.line, use_pyssize_t=True)
|
||||
target_op = empty_op_llbuilder(length, gen.line)
|
||||
|
||||
def set_item(item_index: Value) -> None:
|
||||
e = builder.accept(gen.left_expr)
|
||||
builder.call_c(set_item_op, [target_op, item_index, e], gen.line)
|
||||
|
||||
for_loop_helper_with_index(builder, gen.indices[0], gen.sequences[0], sequence,
|
||||
set_item, gen.line)
|
||||
|
||||
return target_op
|
||||
return None
|
||||
|
||||
|
||||
def translate_list_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Value:
|
||||
# Try simplest list comprehension, otherwise fall back to general one
|
||||
val = sequence_from_generator_preallocate_helper(
|
||||
builder, gen,
|
||||
empty_op_llbuilder=builder.builder.new_list_op_with_length,
|
||||
set_item_op=new_list_set_item_op)
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
list_ops = builder.new_list_op([], gen.line)
|
||||
loop_params = list(zip(gen.indices, gen.sequences, gen.condlists))
|
||||
|
||||
def gen_inner_stmts() -> None:
|
||||
e = builder.accept(gen.left_expr)
|
||||
builder.call_c(list_append_op, [list_ops, e], gen.line)
|
||||
|
||||
comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line)
|
||||
return list_ops
|
||||
|
||||
|
||||
def translate_set_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Value:
|
||||
set_ops = builder.new_set_op([], gen.line)
|
||||
loop_params = list(zip(gen.indices, gen.sequences, gen.condlists))
|
||||
|
||||
def gen_inner_stmts() -> None:
|
||||
e = builder.accept(gen.left_expr)
|
||||
builder.call_c(set_add_op, [set_ops, e], gen.line)
|
||||
|
||||
comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line)
|
||||
return set_ops
|
||||
|
||||
|
||||
def comprehension_helper(builder: IRBuilder,
|
||||
loop_params: List[Tuple[Lvalue, Expression, List[Expression]]],
|
||||
gen_inner_stmts: Callable[[], None],
|
||||
line: int) -> None:
|
||||
"""Helper function for list comprehensions.
|
||||
|
||||
Args:
|
||||
loop_params: a list of (index, expr, [conditions]) tuples defining nested loops:
|
||||
- "index" is the Lvalue indexing that loop;
|
||||
- "expr" is the expression for the object to be iterated over;
|
||||
- "conditions" is a list of conditions, evaluated in order with short-circuiting,
|
||||
that must all be true for the loop body to be executed
|
||||
gen_inner_stmts: function to generate the IR for the body of the innermost loop
|
||||
"""
|
||||
def handle_loop(loop_params: List[Tuple[Lvalue, Expression, List[Expression]]]) -> None:
|
||||
"""Generate IR for a loop.
|
||||
|
||||
Given a list of (index, expression, [conditions]) tuples, generate IR
|
||||
for the nested loops the list defines.
|
||||
"""
|
||||
index, expr, conds = loop_params[0]
|
||||
for_loop_helper(builder, index, expr,
|
||||
lambda: loop_contents(conds, loop_params[1:]),
|
||||
None, line)
|
||||
|
||||
def loop_contents(
|
||||
conds: List[Expression],
|
||||
remaining_loop_params: List[Tuple[Lvalue, Expression, List[Expression]]],
|
||||
) -> None:
|
||||
"""Generate the body of the loop.
|
||||
|
||||
Args:
|
||||
conds: a list of conditions to be evaluated (in order, with short circuiting)
|
||||
to gate the body of the loop
|
||||
remaining_loop_params: the parameters for any further nested loops; if it's empty
|
||||
we'll instead evaluate the "gen_inner_stmts" function
|
||||
"""
|
||||
# Check conditions, in order, short circuiting them.
|
||||
for cond in conds:
|
||||
cond_val = builder.accept(cond)
|
||||
cont_block, rest_block = BasicBlock(), BasicBlock()
|
||||
# If the condition is true we'll skip the continue.
|
||||
builder.add_bool_branch(cond_val, rest_block, cont_block)
|
||||
builder.activate_block(cont_block)
|
||||
builder.nonlocal_control[-1].gen_continue(builder, cond.line)
|
||||
builder.goto_and_activate(rest_block)
|
||||
|
||||
if remaining_loop_params:
|
||||
# There's another nested level, so the body of this loop is another loop.
|
||||
return handle_loop(remaining_loop_params)
|
||||
else:
|
||||
# We finally reached the actual body of the generator.
|
||||
# Generate the IR for the inner loop body.
|
||||
gen_inner_stmts()
|
||||
|
||||
handle_loop(loop_params)
|
||||
|
||||
|
||||
def is_range_ref(expr: RefExpr) -> bool:
|
||||
return (expr.fullname == 'builtins.range'
|
||||
or isinstance(expr.node, TypeAlias) and expr.fullname == 'six.moves.xrange')
|
||||
|
||||
|
||||
def make_for_loop_generator(builder: IRBuilder,
|
||||
index: Lvalue,
|
||||
expr: Expression,
|
||||
body_block: BasicBlock,
|
||||
loop_exit: BasicBlock,
|
||||
line: int,
|
||||
nested: bool = False) -> 'ForGenerator':
|
||||
"""Return helper object for generating a for loop over an iterable.
|
||||
|
||||
If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)".
|
||||
"""
|
||||
|
||||
rtyp = builder.node_type(expr)
|
||||
if is_sequence_rprimitive(rtyp):
|
||||
# Special case "for x in <list>".
|
||||
expr_reg = builder.accept(expr)
|
||||
target_type = builder.get_sequence_type(expr)
|
||||
|
||||
for_list = ForSequence(builder, index, body_block, loop_exit, line, nested)
|
||||
for_list.init(expr_reg, target_type, reverse=False)
|
||||
return for_list
|
||||
|
||||
if is_dict_rprimitive(rtyp):
|
||||
# Special case "for k in <dict>".
|
||||
expr_reg = builder.accept(expr)
|
||||
target_type = builder.get_dict_key_type(expr)
|
||||
|
||||
for_dict = ForDictionaryKeys(builder, index, body_block, loop_exit, line, nested)
|
||||
for_dict.init(expr_reg, target_type)
|
||||
return for_dict
|
||||
|
||||
if (isinstance(expr, CallExpr)
|
||||
and isinstance(expr.callee, RefExpr)):
|
||||
if (is_range_ref(expr.callee)
|
||||
and (len(expr.args) <= 2
|
||||
or (len(expr.args) == 3
|
||||
and builder.extract_int(expr.args[2]) is not None))
|
||||
and set(expr.arg_kinds) == {ARG_POS}):
|
||||
# Special case "for x in range(...)".
|
||||
# We support the 3 arg form but only for int literals, since it doesn't
|
||||
# seem worth the hassle of supporting dynamically determining which
|
||||
# direction of comparison to do.
|
||||
if len(expr.args) == 1:
|
||||
start_reg: Value = Integer(0)
|
||||
end_reg = builder.accept(expr.args[0])
|
||||
else:
|
||||
start_reg = builder.accept(expr.args[0])
|
||||
end_reg = builder.accept(expr.args[1])
|
||||
if len(expr.args) == 3:
|
||||
step = builder.extract_int(expr.args[2])
|
||||
assert step is not None
|
||||
if step == 0:
|
||||
builder.error("range() step can't be zero", expr.args[2].line)
|
||||
else:
|
||||
step = 1
|
||||
|
||||
for_range = ForRange(builder, index, body_block, loop_exit, line, nested)
|
||||
for_range.init(start_reg, end_reg, step)
|
||||
return for_range
|
||||
|
||||
elif (expr.callee.fullname == 'builtins.enumerate'
|
||||
and len(expr.args) == 1
|
||||
and expr.arg_kinds == [ARG_POS]
|
||||
and isinstance(index, TupleExpr)
|
||||
and len(index.items) == 2):
|
||||
# Special case "for i, x in enumerate(y)".
|
||||
lvalue1 = index.items[0]
|
||||
lvalue2 = index.items[1]
|
||||
for_enumerate = ForEnumerate(builder, index, body_block, loop_exit, line,
|
||||
nested)
|
||||
for_enumerate.init(lvalue1, lvalue2, expr.args[0])
|
||||
return for_enumerate
|
||||
|
||||
elif (expr.callee.fullname == 'builtins.zip'
|
||||
and len(expr.args) >= 2
|
||||
and set(expr.arg_kinds) == {ARG_POS}
|
||||
and isinstance(index, TupleExpr)
|
||||
and len(index.items) == len(expr.args)):
|
||||
# Special case "for x, y in zip(a, b)".
|
||||
for_zip = ForZip(builder, index, body_block, loop_exit, line, nested)
|
||||
for_zip.init(index.items, expr.args)
|
||||
return for_zip
|
||||
|
||||
if (expr.callee.fullname == 'builtins.reversed'
|
||||
and len(expr.args) == 1
|
||||
and expr.arg_kinds == [ARG_POS]
|
||||
and is_sequence_rprimitive(builder.node_type(expr.args[0]))):
|
||||
# Special case "for x in reversed(<list>)".
|
||||
expr_reg = builder.accept(expr.args[0])
|
||||
target_type = builder.get_sequence_type(expr)
|
||||
|
||||
for_list = ForSequence(builder, index, body_block, loop_exit, line, nested)
|
||||
for_list.init(expr_reg, target_type, reverse=True)
|
||||
return for_list
|
||||
if (isinstance(expr, CallExpr)
|
||||
and isinstance(expr.callee, MemberExpr)
|
||||
and not expr.args):
|
||||
# Special cases for dictionary iterator methods, like dict.items().
|
||||
rtype = builder.node_type(expr.callee.expr)
|
||||
if (is_dict_rprimitive(rtype)
|
||||
and expr.callee.name in ('keys', 'values', 'items')):
|
||||
expr_reg = builder.accept(expr.callee.expr)
|
||||
for_dict_type: Optional[Type[ForGenerator]] = None
|
||||
if expr.callee.name == 'keys':
|
||||
target_type = builder.get_dict_key_type(expr.callee.expr)
|
||||
for_dict_type = ForDictionaryKeys
|
||||
elif expr.callee.name == 'values':
|
||||
target_type = builder.get_dict_value_type(expr.callee.expr)
|
||||
for_dict_type = ForDictionaryValues
|
||||
else:
|
||||
target_type = builder.get_dict_item_type(expr.callee.expr)
|
||||
for_dict_type = ForDictionaryItems
|
||||
for_dict_gen = for_dict_type(builder, index, body_block, loop_exit, line, nested)
|
||||
for_dict_gen.init(expr_reg, target_type)
|
||||
return for_dict_gen
|
||||
|
||||
# Default to a generic for loop.
|
||||
expr_reg = builder.accept(expr)
|
||||
for_obj = ForIterable(builder, index, body_block, loop_exit, line, nested)
|
||||
item_type = builder._analyze_iterable_item_type(expr)
|
||||
item_rtype = builder.type_to_rtype(item_type)
|
||||
for_obj.init(expr_reg, item_rtype)
|
||||
return for_obj
|
||||
|
||||
|
||||
class ForGenerator:
|
||||
"""Abstract base class for generating for loops."""
|
||||
|
||||
def __init__(self,
|
||||
builder: IRBuilder,
|
||||
index: Lvalue,
|
||||
body_block: BasicBlock,
|
||||
loop_exit: BasicBlock,
|
||||
line: int,
|
||||
nested: bool) -> None:
|
||||
self.builder = builder
|
||||
self.index = index
|
||||
self.body_block = body_block
|
||||
self.line = line
|
||||
# Some for loops need a cleanup block that we execute at exit. We
|
||||
# create a cleanup block if needed. However, if we are generating a for
|
||||
# loop for a nested iterator, such as "e" in "enumerate(e)", the
|
||||
# outermost generator should generate the cleanup block -- we don't
|
||||
# need to do it here.
|
||||
if self.need_cleanup() and not nested:
|
||||
# Create a new block to handle cleanup after loop exit.
|
||||
self.loop_exit = BasicBlock()
|
||||
else:
|
||||
# Just use the existing loop exit block.
|
||||
self.loop_exit = loop_exit
|
||||
|
||||
def need_cleanup(self) -> bool:
|
||||
"""If this returns true, we need post-loop cleanup."""
|
||||
return False
|
||||
|
||||
def add_cleanup(self, exit_block: BasicBlock) -> None:
|
||||
"""Add post-loop cleanup, if needed."""
|
||||
if self.need_cleanup():
|
||||
self.builder.activate_block(self.loop_exit)
|
||||
self.gen_cleanup()
|
||||
self.builder.goto(exit_block)
|
||||
|
||||
def gen_condition(self) -> None:
|
||||
"""Generate check for loop exit (e.g. exhaustion of iteration)."""
|
||||
|
||||
def begin_body(self) -> None:
|
||||
"""Generate ops at the beginning of the body (if needed)."""
|
||||
|
||||
def gen_step(self) -> None:
|
||||
"""Generate stepping to the next item (if needed)."""
|
||||
|
||||
def gen_cleanup(self) -> None:
|
||||
"""Generate post-loop cleanup (if needed)."""
|
||||
|
||||
def load_len(self, expr: Union[Value, AssignmentTarget]) -> Value:
|
||||
"""A helper to get collection length, used by several subclasses."""
|
||||
return self.builder.builder.builtin_len(self.builder.read(expr, self.line), self.line)
|
||||
|
||||
|
||||
class ForIterable(ForGenerator):
|
||||
"""Generate IR for a for loop over an arbitrary iterable (the normal case)."""
|
||||
|
||||
def need_cleanup(self) -> bool:
|
||||
# Create a new cleanup block for when the loop is finished.
|
||||
return True
|
||||
|
||||
def init(self, expr_reg: Value, target_type: RType) -> None:
|
||||
# Define targets to contain the expression, along with the iterator that will be used
|
||||
# for the for-loop. If we are inside of a generator function, spill these into the
|
||||
# environment class.
|
||||
builder = self.builder
|
||||
iter_reg = builder.call_c(iter_op, [expr_reg], self.line)
|
||||
builder.maybe_spill(expr_reg)
|
||||
self.iter_target = builder.maybe_spill(iter_reg)
|
||||
self.target_type = target_type
|
||||
|
||||
def gen_condition(self) -> None:
|
||||
# We call __next__ on the iterator and check to see if the return value
|
||||
# is NULL, which signals either the end of the Iterable being traversed
|
||||
# or an exception being raised. Note that Branch.IS_ERROR checks only
|
||||
# for NULL (an exception does not necessarily have to be raised).
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
self.next_reg = builder.call_c(next_op, [builder.read(self.iter_target, line)], line)
|
||||
builder.add(Branch(self.next_reg, self.loop_exit, self.body_block, Branch.IS_ERROR))
|
||||
|
||||
def begin_body(self) -> None:
|
||||
# Assign the value obtained from __next__ to the
|
||||
# lvalue so that it can be referenced by code in the body of the loop.
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
# We unbox here so that iterating with tuple unpacking generates a tuple based
|
||||
# unpack instead of an iterator based one.
|
||||
next_reg = builder.coerce(self.next_reg, self.target_type, line)
|
||||
builder.assign(builder.get_assignment_target(self.index), next_reg, line)
|
||||
|
||||
def gen_step(self) -> None:
|
||||
# Nothing to do here, since we get the next item as part of gen_condition().
|
||||
pass
|
||||
|
||||
def gen_cleanup(self) -> None:
|
||||
# We set the branch to go here if the conditional evaluates to true. If
|
||||
# an exception was raised during the loop, then err_reg will be set to
|
||||
# True. If no_err_occurred_op returns False, then the exception will be
|
||||
# propagated using the ERR_FALSE flag.
|
||||
self.builder.call_c(no_err_occurred_op, [], self.line)
|
||||
|
||||
|
||||
def unsafe_index(
|
||||
builder: IRBuilder, target: Value, index: Value, line: int
|
||||
) -> Value:
|
||||
"""Emit a potentially unsafe index into a target."""
|
||||
# This doesn't really fit nicely into any of our data-driven frameworks
|
||||
# since we want to use __getitem__ if we don't have an unsafe version,
|
||||
# so we just check manually.
|
||||
if is_list_rprimitive(target.type):
|
||||
return builder.call_c(list_get_item_unsafe_op, [target, index], line)
|
||||
else:
|
||||
return builder.gen_method_call(target, '__getitem__', [index], None, line)
|
||||
|
||||
|
||||
class ForSequence(ForGenerator):
|
||||
"""Generate optimized IR for a for loop over a sequence.
|
||||
|
||||
Supports iterating in both forward and reverse.
|
||||
"""
|
||||
|
||||
def init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None:
|
||||
builder = self.builder
|
||||
self.reverse = reverse
|
||||
# Define target to contain the expression, along with the index that will be used
|
||||
# for the for-loop. If we are inside of a generator function, spill these into the
|
||||
# environment class.
|
||||
self.expr_target = builder.maybe_spill(expr_reg)
|
||||
if not reverse:
|
||||
index_reg: Value = Integer(0)
|
||||
else:
|
||||
index_reg = builder.binary_op(self.load_len(self.expr_target),
|
||||
Integer(1), '-', self.line)
|
||||
self.index_target = builder.maybe_spill_assignable(index_reg)
|
||||
self.target_type = target_type
|
||||
|
||||
def gen_condition(self) -> None:
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
# TODO: Don't reload the length each time when iterating an immutable sequence?
|
||||
if self.reverse:
|
||||
# If we are iterating in reverse order, we obviously need
|
||||
# to check that the index is still positive. Somewhat less
|
||||
# obviously we still need to check against the length,
|
||||
# since it could shrink out from under us.
|
||||
comparison = builder.binary_op(builder.read(self.index_target, line),
|
||||
Integer(0), '>=', line)
|
||||
second_check = BasicBlock()
|
||||
builder.add_bool_branch(comparison, second_check, self.loop_exit)
|
||||
builder.activate_block(second_check)
|
||||
# For compatibility with python semantics we recalculate the length
|
||||
# at every iteration.
|
||||
len_reg = self.load_len(self.expr_target)
|
||||
comparison = builder.binary_op(builder.read(self.index_target, line), len_reg, '<', line)
|
||||
builder.add_bool_branch(comparison, self.body_block, self.loop_exit)
|
||||
|
||||
def begin_body(self) -> None:
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
# Read the next list item.
|
||||
value_box = unsafe_index(
|
||||
builder,
|
||||
builder.read(self.expr_target, line),
|
||||
builder.read(self.index_target, line),
|
||||
line
|
||||
)
|
||||
assert value_box
|
||||
# We coerce to the type of list elements here so that
|
||||
# iterating with tuple unpacking generates a tuple based
|
||||
# unpack instead of an iterator based one.
|
||||
builder.assign(builder.get_assignment_target(self.index),
|
||||
builder.coerce(value_box, self.target_type, line), line)
|
||||
|
||||
def gen_step(self) -> None:
|
||||
# Step to the next item.
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
step = 1 if not self.reverse else -1
|
||||
add = builder.int_op(short_int_rprimitive,
|
||||
builder.read(self.index_target, line),
|
||||
Integer(step), IntOp.ADD, line)
|
||||
builder.assign(self.index_target, add, line)
|
||||
|
||||
|
||||
class ForDictionaryCommon(ForGenerator):
|
||||
"""Generate optimized IR for a for loop over dictionary keys/values.
|
||||
|
||||
The logic is pretty straightforward, we use PyDict_Next() API wrapped in
|
||||
a tuple, so that we can modify only a single register. The layout of the tuple:
|
||||
* f0: are there more items (bool)
|
||||
* f1: current offset (int)
|
||||
* f2: next key (object)
|
||||
* f3: next value (object)
|
||||
For more info see https://docs.python.org/3/c-api/dict.html#c.PyDict_Next.
|
||||
|
||||
Note that for subclasses we fall back to generic PyObject_GetIter() logic,
|
||||
since they may override some iteration methods in subtly incompatible manner.
|
||||
The fallback logic is implemented in CPy.h via dynamic type check.
|
||||
"""
|
||||
|
||||
dict_next_op: ClassVar[CFunctionDescription]
|
||||
dict_iter_op: ClassVar[CFunctionDescription]
|
||||
|
||||
def need_cleanup(self) -> bool:
|
||||
# Technically, a dict subclass can raise an unrelated exception
|
||||
# in __next__(), so we need this.
|
||||
return True
|
||||
|
||||
def init(self, expr_reg: Value, target_type: RType) -> None:
|
||||
builder = self.builder
|
||||
self.target_type = target_type
|
||||
|
||||
# We add some variables to environment class, so they can be read across yield.
|
||||
self.expr_target = builder.maybe_spill(expr_reg)
|
||||
offset = Integer(0)
|
||||
self.offset_target = builder.maybe_spill_assignable(offset)
|
||||
self.size = builder.maybe_spill(self.load_len(self.expr_target))
|
||||
|
||||
# For dict class (not a subclass) this is the dictionary itself.
|
||||
iter_reg = builder.call_c(self.dict_iter_op, [expr_reg], self.line)
|
||||
self.iter_target = builder.maybe_spill(iter_reg)
|
||||
|
||||
def gen_condition(self) -> None:
|
||||
"""Get next key/value pair, set new offset, and check if we should continue."""
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
self.next_tuple = self.builder.call_c(
|
||||
self.dict_next_op, [builder.read(self.iter_target, line),
|
||||
builder.read(self.offset_target, line)], line)
|
||||
|
||||
# Do this here instead of in gen_step() to minimize variables in environment.
|
||||
new_offset = builder.add(TupleGet(self.next_tuple, 1, line))
|
||||
builder.assign(self.offset_target, new_offset, line)
|
||||
|
||||
should_continue = builder.add(TupleGet(self.next_tuple, 0, line))
|
||||
builder.add(
|
||||
Branch(should_continue, self.body_block, self.loop_exit, Branch.BOOL)
|
||||
)
|
||||
|
||||
def gen_step(self) -> None:
|
||||
"""Check that dictionary didn't change size during iteration.
|
||||
|
||||
Raise RuntimeError if it is not the case to match CPython behavior.
|
||||
"""
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
# Technically, we don't need a new primitive for this, but it is simpler.
|
||||
builder.call_c(dict_check_size_op,
|
||||
[builder.read(self.expr_target, line),
|
||||
builder.read(self.size, line)], line)
|
||||
|
||||
def gen_cleanup(self) -> None:
|
||||
# Same as for generic ForIterable.
|
||||
self.builder.call_c(no_err_occurred_op, [], self.line)
|
||||
|
||||
|
||||
class ForDictionaryKeys(ForDictionaryCommon):
|
||||
"""Generate optimized IR for a for loop over dictionary keys."""
|
||||
dict_next_op = dict_next_key_op
|
||||
dict_iter_op = dict_key_iter_op
|
||||
|
||||
def begin_body(self) -> None:
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
|
||||
# Key is stored at the third place in the tuple.
|
||||
key = builder.add(TupleGet(self.next_tuple, 2, line))
|
||||
builder.assign(builder.get_assignment_target(self.index),
|
||||
builder.coerce(key, self.target_type, line), line)
|
||||
|
||||
|
||||
class ForDictionaryValues(ForDictionaryCommon):
|
||||
"""Generate optimized IR for a for loop over dictionary values."""
|
||||
dict_next_op = dict_next_value_op
|
||||
dict_iter_op = dict_value_iter_op
|
||||
|
||||
def begin_body(self) -> None:
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
|
||||
# Value is stored at the third place in the tuple.
|
||||
value = builder.add(TupleGet(self.next_tuple, 2, line))
|
||||
builder.assign(builder.get_assignment_target(self.index),
|
||||
builder.coerce(value, self.target_type, line), line)
|
||||
|
||||
|
||||
class ForDictionaryItems(ForDictionaryCommon):
|
||||
"""Generate optimized IR for a for loop over dictionary items."""
|
||||
dict_next_op = dict_next_item_op
|
||||
dict_iter_op = dict_item_iter_op
|
||||
|
||||
def begin_body(self) -> None:
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
|
||||
key = builder.add(TupleGet(self.next_tuple, 2, line))
|
||||
value = builder.add(TupleGet(self.next_tuple, 3, line))
|
||||
|
||||
# Coerce just in case e.g. key is itself a tuple to be unpacked.
|
||||
assert isinstance(self.target_type, RTuple)
|
||||
key = builder.coerce(key, self.target_type.types[0], line)
|
||||
value = builder.coerce(value, self.target_type.types[1], line)
|
||||
|
||||
target = builder.get_assignment_target(self.index)
|
||||
if isinstance(target, AssignmentTargetTuple):
|
||||
# Simpler code for common case: for k, v in d.items().
|
||||
if len(target.items) != 2:
|
||||
builder.error("Expected a pair for dict item iteration", line)
|
||||
builder.assign(target.items[0], key, line)
|
||||
builder.assign(target.items[1], value, line)
|
||||
else:
|
||||
rvalue = builder.add(TupleSet([key, value], line))
|
||||
builder.assign(target, rvalue, line)
|
||||
|
||||
|
||||
class ForRange(ForGenerator):
|
||||
"""Generate optimized IR for a for loop over an integer range."""
|
||||
|
||||
def init(self, start_reg: Value, end_reg: Value, step: int) -> None:
|
||||
builder = self.builder
|
||||
self.start_reg = start_reg
|
||||
self.end_reg = end_reg
|
||||
self.step = step
|
||||
self.end_target = builder.maybe_spill(end_reg)
|
||||
if is_short_int_rprimitive(start_reg.type) and is_short_int_rprimitive(end_reg.type):
|
||||
index_type = short_int_rprimitive
|
||||
else:
|
||||
index_type = int_rprimitive
|
||||
index_reg = Register(index_type)
|
||||
builder.assign(index_reg, start_reg, -1)
|
||||
self.index_reg = builder.maybe_spill_assignable(index_reg)
|
||||
# Initialize loop index to 0. Assert that the index target is assignable.
|
||||
self.index_target: Union[Register, AssignmentTarget] = builder.get_assignment_target(
|
||||
self.index
|
||||
)
|
||||
builder.assign(self.index_target, builder.read(self.index_reg, self.line), self.line)
|
||||
|
||||
def gen_condition(self) -> None:
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
# Add loop condition check.
|
||||
cmp = '<' if self.step > 0 else '>'
|
||||
comparison = builder.binary_op(builder.read(self.index_reg, line),
|
||||
builder.read(self.end_target, line), cmp, line)
|
||||
builder.add_bool_branch(comparison, self.body_block, self.loop_exit)
|
||||
|
||||
def gen_step(self) -> None:
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
|
||||
# Increment index register. If the range is known to fit in short ints, use
|
||||
# short ints.
|
||||
if (is_short_int_rprimitive(self.start_reg.type)
|
||||
and is_short_int_rprimitive(self.end_reg.type)):
|
||||
new_val = builder.int_op(short_int_rprimitive,
|
||||
builder.read(self.index_reg, line),
|
||||
Integer(self.step), IntOp.ADD, line)
|
||||
|
||||
else:
|
||||
new_val = builder.binary_op(
|
||||
builder.read(self.index_reg, line), Integer(self.step), '+', line)
|
||||
builder.assign(self.index_reg, new_val, line)
|
||||
builder.assign(self.index_target, new_val, line)
|
||||
|
||||
|
||||
class ForInfiniteCounter(ForGenerator):
|
||||
"""Generate optimized IR for a for loop counting from 0 to infinity."""
|
||||
|
||||
def init(self) -> None:
|
||||
builder = self.builder
|
||||
# Create a register to store the state of the loop index and
|
||||
# initialize this register along with the loop index to 0.
|
||||
zero = Integer(0)
|
||||
self.index_reg = builder.maybe_spill_assignable(zero)
|
||||
self.index_target: Union[Register, AssignmentTarget] = builder.get_assignment_target(
|
||||
self.index
|
||||
)
|
||||
builder.assign(self.index_target, zero, self.line)
|
||||
|
||||
def gen_step(self) -> None:
|
||||
builder = self.builder
|
||||
line = self.line
|
||||
# We can safely assume that the integer is short, since we are not going to wrap
|
||||
# around a 63-bit integer.
|
||||
# NOTE: This would be questionable if short ints could be 32 bits.
|
||||
new_val = builder.int_op(short_int_rprimitive,
|
||||
builder.read(self.index_reg, line),
|
||||
Integer(1), IntOp.ADD, line)
|
||||
builder.assign(self.index_reg, new_val, line)
|
||||
builder.assign(self.index_target, new_val, line)
|
||||
|
||||
|
||||
class ForEnumerate(ForGenerator):
|
||||
"""Generate optimized IR for a for loop of form "for i, x in enumerate(it)"."""
|
||||
|
||||
def need_cleanup(self) -> bool:
|
||||
# The wrapped for loop might need cleanup. This might generate a
|
||||
# redundant cleanup block, but that's okay.
|
||||
return True
|
||||
|
||||
def init(self, index1: Lvalue, index2: Lvalue, expr: Expression) -> None:
|
||||
# Count from 0 to infinity (for the index lvalue).
|
||||
self.index_gen = ForInfiniteCounter(
|
||||
self.builder,
|
||||
index1,
|
||||
self.body_block,
|
||||
self.loop_exit,
|
||||
self.line, nested=True)
|
||||
self.index_gen.init()
|
||||
# Iterate over the actual iterable.
|
||||
self.main_gen = make_for_loop_generator(
|
||||
self.builder,
|
||||
index2,
|
||||
expr,
|
||||
self.body_block,
|
||||
self.loop_exit,
|
||||
self.line, nested=True)
|
||||
|
||||
def gen_condition(self) -> None:
|
||||
# No need for a check for the index generator, since it's unconditional.
|
||||
self.main_gen.gen_condition()
|
||||
|
||||
def begin_body(self) -> None:
|
||||
self.index_gen.begin_body()
|
||||
self.main_gen.begin_body()
|
||||
|
||||
def gen_step(self) -> None:
|
||||
self.index_gen.gen_step()
|
||||
self.main_gen.gen_step()
|
||||
|
||||
def gen_cleanup(self) -> None:
|
||||
self.index_gen.gen_cleanup()
|
||||
self.main_gen.gen_cleanup()
|
||||
|
||||
|
||||
class ForZip(ForGenerator):
|
||||
"""Generate IR for a for loop of form `for x, ... in zip(a, ...)`."""
|
||||
|
||||
def need_cleanup(self) -> bool:
|
||||
# The wrapped for loops might need cleanup. We might generate a
|
||||
# redundant cleanup block, but that's okay.
|
||||
return True
|
||||
|
||||
def init(self, indexes: List[Lvalue], exprs: List[Expression]) -> None:
|
||||
assert len(indexes) == len(exprs)
|
||||
# Condition check will require multiple basic blocks, since there will be
|
||||
# multiple conditions to check.
|
||||
self.cond_blocks = [BasicBlock() for _ in range(len(indexes) - 1)] + [self.body_block]
|
||||
self.gens: List[ForGenerator] = []
|
||||
for index, expr, next_block in zip(indexes, exprs, self.cond_blocks):
|
||||
gen = make_for_loop_generator(
|
||||
self.builder,
|
||||
index,
|
||||
expr,
|
||||
next_block,
|
||||
self.loop_exit,
|
||||
self.line, nested=True)
|
||||
self.gens.append(gen)
|
||||
|
||||
def gen_condition(self) -> None:
|
||||
for i, gen in enumerate(self.gens):
|
||||
gen.gen_condition()
|
||||
if i < len(self.gens) - 1:
|
||||
self.builder.activate_block(self.cond_blocks[i])
|
||||
|
||||
def begin_body(self) -> None:
|
||||
for gen in self.gens:
|
||||
gen.begin_body()
|
||||
|
||||
def gen_step(self) -> None:
|
||||
for gen in self.gens:
|
||||
gen.gen_step()
|
||||
|
||||
def gen_cleanup(self) -> None:
|
||||
for gen in self.gens:
|
||||
gen.gen_cleanup()
|
||||
Binary file not shown.
|
|
@ -0,0 +1,239 @@
|
|||
"""Tokenizers for three string formatting methods"""
|
||||
|
||||
from typing import List, Tuple, Optional
|
||||
from typing_extensions import Final
|
||||
from enum import Enum, unique
|
||||
|
||||
from mypy.checkstrformat import (
|
||||
parse_format_value, ConversionSpecifier, parse_conversion_specifiers
|
||||
)
|
||||
from mypy.errors import Errors
|
||||
from mypy.messages import MessageBuilder
|
||||
from mypy.nodes import Context, Expression
|
||||
|
||||
from mypyc.ir.ops import Value, Integer
|
||||
from mypyc.ir.rtypes import (
|
||||
c_pyssize_t_rprimitive, is_str_rprimitive, is_int_rprimitive, is_short_int_rprimitive,
|
||||
is_bytes_rprimitive
|
||||
)
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
from mypyc.primitives.bytes_ops import bytes_build_op
|
||||
from mypyc.primitives.int_ops import int_to_str_op
|
||||
from mypyc.primitives.str_ops import str_build_op, str_op
|
||||
|
||||
|
||||
@unique
|
||||
class FormatOp(Enum):
|
||||
"""FormatOp represents conversion operations of string formatting during
|
||||
compile time.
|
||||
|
||||
Compare to ConversionSpecifier, FormatOp has fewer attributes.
|
||||
For example, to mark a conversion from any object to string,
|
||||
ConversionSpecifier may have several representations, like '%s', '{}'
|
||||
or '{:{}}'. However, there would only exist one corresponding FormatOp.
|
||||
"""
|
||||
STR = 's'
|
||||
INT = 'd'
|
||||
BYTES = 'b'
|
||||
|
||||
|
||||
def generate_format_ops(specifiers: List[ConversionSpecifier]) -> Optional[List[FormatOp]]:
|
||||
"""Convert ConversionSpecifier to FormatOp.
|
||||
|
||||
Different ConversionSpecifiers may share a same FormatOp.
|
||||
"""
|
||||
format_ops = []
|
||||
for spec in specifiers:
|
||||
# TODO: Match specifiers instead of using whole_seq
|
||||
if spec.whole_seq == '%s' or spec.whole_seq == '{:{}}':
|
||||
format_op = FormatOp.STR
|
||||
elif spec.whole_seq == '%d':
|
||||
format_op = FormatOp.INT
|
||||
elif spec.whole_seq == '%b':
|
||||
format_op = FormatOp.BYTES
|
||||
elif spec.whole_seq:
|
||||
return None
|
||||
else:
|
||||
format_op = FormatOp.STR
|
||||
format_ops.append(format_op)
|
||||
return format_ops
|
||||
|
||||
|
||||
def tokenizer_printf_style(format_str: str) -> Optional[Tuple[List[str], List[FormatOp]]]:
|
||||
"""Tokenize a printf-style format string using regex.
|
||||
|
||||
Return:
|
||||
A list of string literals and a list of FormatOps.
|
||||
"""
|
||||
literals: List[str] = []
|
||||
specifiers: List[ConversionSpecifier] = parse_conversion_specifiers(format_str)
|
||||
format_ops = generate_format_ops(specifiers)
|
||||
if format_ops is None:
|
||||
return None
|
||||
|
||||
last_end = 0
|
||||
for spec in specifiers:
|
||||
cur_start = spec.start_pos
|
||||
literals.append(format_str[last_end:cur_start])
|
||||
last_end = cur_start + len(spec.whole_seq)
|
||||
literals.append(format_str[last_end:])
|
||||
|
||||
return literals, format_ops
|
||||
|
||||
|
||||
# The empty Context as an argument for parse_format_value().
|
||||
# It wouldn't be used since the code has passed the type-checking.
|
||||
EMPTY_CONTEXT: Final = Context()
|
||||
|
||||
|
||||
def tokenizer_format_call(
|
||||
format_str: str) -> Optional[Tuple[List[str], List[FormatOp]]]:
|
||||
"""Tokenize a str.format() format string.
|
||||
|
||||
The core function parse_format_value() is shared with mypy.
|
||||
With these specifiers, we then parse the literal substrings
|
||||
of the original format string and convert `ConversionSpecifier`
|
||||
to `FormatOp`.
|
||||
|
||||
Return:
|
||||
A list of string literals and a list of FormatOps. The literals
|
||||
are interleaved with FormatOps and the length of returned literals
|
||||
should be exactly one more than FormatOps.
|
||||
Return None if it cannot parse the string.
|
||||
"""
|
||||
# Creates an empty MessageBuilder here.
|
||||
# It wouldn't be used since the code has passed the type-checking.
|
||||
specifiers = parse_format_value(format_str, EMPTY_CONTEXT,
|
||||
MessageBuilder(Errors(), {}))
|
||||
if specifiers is None:
|
||||
return None
|
||||
format_ops = generate_format_ops(specifiers)
|
||||
if format_ops is None:
|
||||
return None
|
||||
|
||||
literals: List[str] = []
|
||||
last_end = 0
|
||||
for spec in specifiers:
|
||||
# Skip { and }
|
||||
literals.append(format_str[last_end:spec.start_pos - 1])
|
||||
last_end = spec.start_pos + len(spec.whole_seq) + 1
|
||||
literals.append(format_str[last_end:])
|
||||
# Deal with escaped {{
|
||||
literals = [x.replace('{{', '{').replace('}}', '}') for x in literals]
|
||||
|
||||
return literals, format_ops
|
||||
|
||||
|
||||
def convert_format_expr_to_str(builder: IRBuilder, format_ops: List[FormatOp],
|
||||
exprs: List[Expression], line: int) -> Optional[List[Value]]:
|
||||
"""Convert expressions into string literal objects with the guidance
|
||||
of FormatOps. Return None when fails."""
|
||||
if len(format_ops) != len(exprs):
|
||||
return None
|
||||
|
||||
converted = []
|
||||
for x, format_op in zip(exprs, format_ops):
|
||||
node_type = builder.node_type(x)
|
||||
if format_op == FormatOp.STR:
|
||||
if is_str_rprimitive(node_type):
|
||||
var_str = builder.accept(x)
|
||||
elif is_int_rprimitive(node_type) or is_short_int_rprimitive(node_type):
|
||||
var_str = builder.call_c(int_to_str_op, [builder.accept(x)], line)
|
||||
else:
|
||||
var_str = builder.call_c(str_op, [builder.accept(x)], line)
|
||||
elif format_op == FormatOp.INT:
|
||||
if is_int_rprimitive(node_type) or is_short_int_rprimitive(node_type):
|
||||
var_str = builder.call_c(int_to_str_op, [builder.accept(x)], line)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
converted.append(var_str)
|
||||
return converted
|
||||
|
||||
|
||||
def join_formatted_strings(builder: IRBuilder, literals: Optional[List[str]],
|
||||
substitutions: List[Value], line: int) -> Value:
|
||||
"""Merge the list of literals and the list of substitutions
|
||||
alternatively using 'str_build_op'.
|
||||
|
||||
`substitutions` is the result value of formatting conversions.
|
||||
|
||||
If the `literals` is set to None, we simply join the substitutions;
|
||||
Otherwise, the `literals` is the literal substrings of the original
|
||||
format string and its length should be exactly one more than
|
||||
substitutions.
|
||||
|
||||
For example:
|
||||
(1) 'This is a %s and the value is %d'
|
||||
-> literals: ['This is a ', ' and the value is', '']
|
||||
(2) '{} and the value is {}'
|
||||
-> literals: ['', ' and the value is', '']
|
||||
"""
|
||||
# The first parameter for str_build_op is the total size of
|
||||
# the following PyObject*
|
||||
result_list: List[Value] = [Integer(0, c_pyssize_t_rprimitive)]
|
||||
|
||||
if literals is not None:
|
||||
for a, b in zip(literals, substitutions):
|
||||
if a:
|
||||
result_list.append(builder.load_str(a))
|
||||
result_list.append(b)
|
||||
if literals[-1]:
|
||||
result_list.append(builder.load_str(literals[-1]))
|
||||
else:
|
||||
result_list.extend(substitutions)
|
||||
|
||||
# Special case for empty string and literal string
|
||||
if len(result_list) == 1:
|
||||
return builder.load_str("")
|
||||
if not substitutions and len(result_list) == 2:
|
||||
return result_list[1]
|
||||
|
||||
result_list[0] = Integer(len(result_list) - 1, c_pyssize_t_rprimitive)
|
||||
return builder.call_c(str_build_op, result_list, line)
|
||||
|
||||
|
||||
def convert_format_expr_to_bytes(builder: IRBuilder, format_ops: List[FormatOp],
|
||||
exprs: List[Expression], line: int) -> Optional[List[Value]]:
|
||||
"""Convert expressions into bytes literal objects with the guidance
|
||||
of FormatOps. Return None when fails."""
|
||||
if len(format_ops) != len(exprs):
|
||||
return None
|
||||
|
||||
converted = []
|
||||
for x, format_op in zip(exprs, format_ops):
|
||||
node_type = builder.node_type(x)
|
||||
# conversion type 's' is an alias of 'b' in bytes formatting
|
||||
if format_op == FormatOp.BYTES or format_op == FormatOp.STR:
|
||||
if is_bytes_rprimitive(node_type):
|
||||
var_bytes = builder.accept(x)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
converted.append(var_bytes)
|
||||
return converted
|
||||
|
||||
|
||||
def join_formatted_bytes(builder: IRBuilder, literals: List[str],
|
||||
substitutions: List[Value], line: int) -> Value:
|
||||
"""Merge the list of literals and the list of substitutions
|
||||
alternatively using 'bytes_build_op'."""
|
||||
result_list: List[Value] = [Integer(0, c_pyssize_t_rprimitive)]
|
||||
|
||||
for a, b in zip(literals, substitutions):
|
||||
if a:
|
||||
result_list.append(builder.load_bytes_from_str_literal(a))
|
||||
result_list.append(b)
|
||||
if literals[-1]:
|
||||
result_list.append(builder.load_bytes_from_str_literal(literals[-1]))
|
||||
|
||||
# Special case for empty bytes and literal
|
||||
if len(result_list) == 1:
|
||||
return builder.load_bytes_from_str_literal('')
|
||||
if not substitutions and len(result_list) == 2:
|
||||
return result_list[1]
|
||||
|
||||
result_list[0] = Integer(len(result_list) - 1, c_pyssize_t_rprimitive)
|
||||
return builder.call_c(bytes_build_op, result_list, line)
|
||||
Binary file not shown.
1057
.venv/lib/python3.8/site-packages/mypyc/irbuild/function.py
Normal file
1057
.venv/lib/python3.8/site-packages/mypyc/irbuild/function.py
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
272
.venv/lib/python3.8/site-packages/mypyc/irbuild/generator.py
Normal file
272
.venv/lib/python3.8/site-packages/mypyc/irbuild/generator.py
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
"""Generate IR for generator functions.
|
||||
|
||||
A generator function is represented by a class that implements the
|
||||
generator protocol and keeps track of the generator state, including
|
||||
local variables.
|
||||
|
||||
The top-level logic for dealing with generator functions is in
|
||||
mypyc.irbuild.function.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
|
||||
from mypy.nodes import Var, ARG_OPT
|
||||
|
||||
from mypyc.common import SELF_NAME, NEXT_LABEL_ATTR_NAME, ENV_ATTR_NAME
|
||||
from mypyc.ir.ops import (
|
||||
BasicBlock, Call, Return, Goto, Integer, SetAttr, Unreachable, RaiseStandardError,
|
||||
Value, Register
|
||||
)
|
||||
from mypyc.ir.rtypes import RInstance, int_rprimitive, object_rprimitive
|
||||
from mypyc.ir.func_ir import FuncIR, FuncDecl, FuncSignature, RuntimeArg
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.primitives.exc_ops import raise_exception_with_tb_op
|
||||
from mypyc.irbuild.env_class import (
|
||||
add_args_to_env, load_outer_env, load_env_registers, finalize_env_class
|
||||
)
|
||||
from mypyc.irbuild.builder import IRBuilder, gen_arg_defaults
|
||||
from mypyc.irbuild.context import FuncInfo, GeneratorClass
|
||||
|
||||
|
||||
def gen_generator_func(builder: IRBuilder) -> None:
|
||||
setup_generator_class(builder)
|
||||
load_env_registers(builder)
|
||||
gen_arg_defaults(builder)
|
||||
finalize_env_class(builder)
|
||||
builder.add(Return(instantiate_generator_class(builder)))
|
||||
|
||||
|
||||
def instantiate_generator_class(builder: IRBuilder) -> Value:
|
||||
fitem = builder.fn_info.fitem
|
||||
generator_reg = builder.add(Call(builder.fn_info.generator_class.ir.ctor, [], fitem.line))
|
||||
|
||||
# Get the current environment register. If the current function is nested, then the
|
||||
# generator class gets instantiated from the callable class' '__call__' method, and hence
|
||||
# we use the callable class' environment register. Otherwise, we use the original
|
||||
# function's environment register.
|
||||
if builder.fn_info.is_nested:
|
||||
curr_env_reg = builder.fn_info.callable_class.curr_env_reg
|
||||
else:
|
||||
curr_env_reg = builder.fn_info.curr_env_reg
|
||||
|
||||
# Set the generator class' environment attribute to point at the environment class
|
||||
# defined in the current scope.
|
||||
builder.add(SetAttr(generator_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line))
|
||||
|
||||
# Set the generator class' environment class' NEXT_LABEL_ATTR_NAME attribute to 0.
|
||||
zero = Integer(0)
|
||||
builder.add(SetAttr(curr_env_reg, NEXT_LABEL_ATTR_NAME, zero, fitem.line))
|
||||
return generator_reg
|
||||
|
||||
|
||||
def setup_generator_class(builder: IRBuilder) -> ClassIR:
|
||||
name = '{}_gen'.format(builder.fn_info.namespaced_name())
|
||||
|
||||
generator_class_ir = ClassIR(name, builder.module_name, is_generated=True)
|
||||
generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class)
|
||||
generator_class_ir.mro = [generator_class_ir]
|
||||
|
||||
builder.classes.append(generator_class_ir)
|
||||
builder.fn_info.generator_class = GeneratorClass(generator_class_ir)
|
||||
return generator_class_ir
|
||||
|
||||
|
||||
def create_switch_for_generator_class(builder: IRBuilder) -> None:
|
||||
builder.add(Goto(builder.fn_info.generator_class.switch_block))
|
||||
block = BasicBlock()
|
||||
builder.fn_info.generator_class.continuation_blocks.append(block)
|
||||
builder.activate_block(block)
|
||||
|
||||
|
||||
def populate_switch_for_generator_class(builder: IRBuilder) -> None:
|
||||
cls = builder.fn_info.generator_class
|
||||
line = builder.fn_info.fitem.line
|
||||
|
||||
builder.activate_block(cls.switch_block)
|
||||
for label, true_block in enumerate(cls.continuation_blocks):
|
||||
false_block = BasicBlock()
|
||||
comparison = builder.binary_op(
|
||||
cls.next_label_reg, Integer(label), '==', line
|
||||
)
|
||||
builder.add_bool_branch(comparison, true_block, false_block)
|
||||
builder.activate_block(false_block)
|
||||
|
||||
builder.add(RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, line))
|
||||
builder.add(Unreachable())
|
||||
|
||||
|
||||
def add_raise_exception_blocks_to_generator_class(builder: IRBuilder, line: int) -> None:
|
||||
"""Add error handling blocks to a generator class.
|
||||
|
||||
Generates blocks to check if error flags are set while calling the
|
||||
helper method for generator functions, and raises an exception if
|
||||
those flags are set.
|
||||
"""
|
||||
cls = builder.fn_info.generator_class
|
||||
assert cls.exc_regs is not None
|
||||
exc_type, exc_val, exc_tb = cls.exc_regs
|
||||
|
||||
# Check to see if an exception was raised.
|
||||
error_block = BasicBlock()
|
||||
ok_block = BasicBlock()
|
||||
comparison = builder.translate_is_op(exc_type, builder.none_object(), 'is not', line)
|
||||
builder.add_bool_branch(comparison, error_block, ok_block)
|
||||
|
||||
builder.activate_block(error_block)
|
||||
builder.call_c(raise_exception_with_tb_op, [exc_type, exc_val, exc_tb], line)
|
||||
builder.add(Unreachable())
|
||||
builder.goto_and_activate(ok_block)
|
||||
|
||||
|
||||
def add_methods_to_generator_class(builder: IRBuilder,
|
||||
fn_info: FuncInfo,
|
||||
sig: FuncSignature,
|
||||
arg_regs: List[Register],
|
||||
blocks: List[BasicBlock],
|
||||
is_coroutine: bool) -> None:
|
||||
helper_fn_decl = add_helper_to_generator_class(builder, arg_regs, blocks, sig, fn_info)
|
||||
add_next_to_generator_class(builder, fn_info, helper_fn_decl, sig)
|
||||
add_send_to_generator_class(builder, fn_info, helper_fn_decl, sig)
|
||||
add_iter_to_generator_class(builder, fn_info)
|
||||
add_throw_to_generator_class(builder, fn_info, helper_fn_decl, sig)
|
||||
add_close_to_generator_class(builder, fn_info)
|
||||
if is_coroutine:
|
||||
add_await_to_generator_class(builder, fn_info)
|
||||
|
||||
|
||||
def add_helper_to_generator_class(builder: IRBuilder,
|
||||
arg_regs: List[Register],
|
||||
blocks: List[BasicBlock],
|
||||
sig: FuncSignature,
|
||||
fn_info: FuncInfo) -> FuncDecl:
|
||||
"""Generates a helper method for a generator class, called by '__next__' and 'throw'."""
|
||||
sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive),
|
||||
RuntimeArg('type', object_rprimitive),
|
||||
RuntimeArg('value', object_rprimitive),
|
||||
RuntimeArg('traceback', object_rprimitive),
|
||||
RuntimeArg('arg', object_rprimitive)
|
||||
), sig.ret_type)
|
||||
helper_fn_decl = FuncDecl('__mypyc_generator_helper__', fn_info.generator_class.ir.name,
|
||||
builder.module_name, sig)
|
||||
helper_fn_ir = FuncIR(helper_fn_decl, arg_regs, blocks,
|
||||
fn_info.fitem.line, traceback_name=fn_info.fitem.name)
|
||||
fn_info.generator_class.ir.methods['__mypyc_generator_helper__'] = helper_fn_ir
|
||||
builder.functions.append(helper_fn_ir)
|
||||
return helper_fn_decl
|
||||
|
||||
|
||||
def add_iter_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
|
||||
"""Generates the '__iter__' method for a generator class."""
|
||||
with builder.enter_method(fn_info.generator_class.ir, '__iter__', object_rprimitive, fn_info):
|
||||
builder.add(Return(builder.self()))
|
||||
|
||||
|
||||
def add_next_to_generator_class(builder: IRBuilder,
|
||||
fn_info: FuncInfo,
|
||||
fn_decl: FuncDecl,
|
||||
sig: FuncSignature) -> None:
|
||||
"""Generates the '__next__' method for a generator class."""
|
||||
with builder.enter_method(fn_info.generator_class.ir, '__next__',
|
||||
object_rprimitive, fn_info):
|
||||
none_reg = builder.none_object()
|
||||
# Call the helper function with error flags set to Py_None, and return that result.
|
||||
result = builder.add(Call(fn_decl,
|
||||
[builder.self(), none_reg, none_reg, none_reg, none_reg],
|
||||
fn_info.fitem.line))
|
||||
builder.add(Return(result))
|
||||
|
||||
|
||||
def add_send_to_generator_class(builder: IRBuilder,
|
||||
fn_info: FuncInfo,
|
||||
fn_decl: FuncDecl,
|
||||
sig: FuncSignature) -> None:
|
||||
"""Generates the 'send' method for a generator class."""
|
||||
with builder.enter_method(fn_info.generator_class.ir, 'send', object_rprimitive, fn_info):
|
||||
arg = builder.add_argument('arg', object_rprimitive)
|
||||
none_reg = builder.none_object()
|
||||
# Call the helper function with error flags set to Py_None, and return that result.
|
||||
result = builder.add(Call(
|
||||
fn_decl,
|
||||
[builder.self(), none_reg, none_reg, none_reg, builder.read(arg)],
|
||||
fn_info.fitem.line))
|
||||
builder.add(Return(result))
|
||||
|
||||
|
||||
def add_throw_to_generator_class(builder: IRBuilder,
|
||||
fn_info: FuncInfo,
|
||||
fn_decl: FuncDecl,
|
||||
sig: FuncSignature) -> None:
|
||||
"""Generates the 'throw' method for a generator class."""
|
||||
with builder.enter_method(fn_info.generator_class.ir, 'throw',
|
||||
object_rprimitive, fn_info):
|
||||
typ = builder.add_argument('type', object_rprimitive)
|
||||
val = builder.add_argument('value', object_rprimitive, ARG_OPT)
|
||||
tb = builder.add_argument('traceback', object_rprimitive, ARG_OPT)
|
||||
|
||||
# Because the value and traceback arguments are optional and hence
|
||||
# can be NULL if not passed in, we have to assign them Py_None if
|
||||
# they are not passed in.
|
||||
none_reg = builder.none_object()
|
||||
builder.assign_if_null(val, lambda: none_reg, builder.fn_info.fitem.line)
|
||||
builder.assign_if_null(tb, lambda: none_reg, builder.fn_info.fitem.line)
|
||||
|
||||
# Call the helper function using the arguments passed in, and return that result.
|
||||
result = builder.add(Call(
|
||||
fn_decl,
|
||||
[builder.self(), builder.read(typ), builder.read(val), builder.read(tb), none_reg],
|
||||
fn_info.fitem.line))
|
||||
builder.add(Return(result))
|
||||
|
||||
|
||||
def add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
|
||||
"""Generates the '__close__' method for a generator class."""
|
||||
# TODO: Currently this method just triggers a runtime error.
|
||||
# We should fill this out (https://github.com/mypyc/mypyc/issues/790).
|
||||
with builder.enter_method(fn_info.generator_class.ir, 'close', object_rprimitive, fn_info):
|
||||
builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR,
|
||||
'close method on generator classes unimplemented',
|
||||
fn_info.fitem.line))
|
||||
builder.add(Unreachable())
|
||||
|
||||
|
||||
def add_await_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
|
||||
"""Generates the '__await__' method for a generator class."""
|
||||
with builder.enter_method(fn_info.generator_class.ir, '__await__', object_rprimitive, fn_info):
|
||||
builder.add(Return(builder.self()))
|
||||
|
||||
|
||||
def setup_env_for_generator_class(builder: IRBuilder) -> None:
|
||||
"""Populates the environment for a generator class."""
|
||||
fitem = builder.fn_info.fitem
|
||||
cls = builder.fn_info.generator_class
|
||||
self_target = builder.add_self_to_env(cls.ir)
|
||||
|
||||
# Add the type, value, and traceback variables to the environment.
|
||||
exc_type = builder.add_local(Var('type'), object_rprimitive, is_arg=True)
|
||||
exc_val = builder.add_local(Var('value'), object_rprimitive, is_arg=True)
|
||||
exc_tb = builder.add_local(Var('traceback'), object_rprimitive, is_arg=True)
|
||||
# TODO: Use the right type here instead of object?
|
||||
exc_arg = builder.add_local(Var('arg'), object_rprimitive, is_arg=True)
|
||||
|
||||
cls.exc_regs = (exc_type, exc_val, exc_tb)
|
||||
cls.send_arg_reg = exc_arg
|
||||
|
||||
cls.self_reg = builder.read(self_target, fitem.line)
|
||||
cls.curr_env_reg = load_outer_env(builder, cls.self_reg, builder.symtables[-1])
|
||||
|
||||
# Define a variable representing the label to go to the next time
|
||||
# the '__next__' function of the generator is called, and add it
|
||||
# as an attribute to the environment class.
|
||||
cls.next_label_target = builder.add_var_to_env_class(
|
||||
Var(NEXT_LABEL_ATTR_NAME),
|
||||
int_rprimitive,
|
||||
cls,
|
||||
reassign=False
|
||||
)
|
||||
|
||||
# Add arguments from the original generator function to the
|
||||
# environment of the generator class.
|
||||
add_args_to_env(builder, local=False, base=cls, reassign=False)
|
||||
|
||||
# Set the next label register for the generator class.
|
||||
cls.next_label_reg = builder.read(cls.next_label_target, fitem.line)
|
||||
Binary file not shown.
1582
.venv/lib/python3.8/site-packages/mypyc/irbuild/ll_builder.py
Normal file
1582
.venv/lib/python3.8/site-packages/mypyc/irbuild/ll_builder.py
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
134
.venv/lib/python3.8/site-packages/mypyc/irbuild/main.py
Normal file
134
.venv/lib/python3.8/site-packages/mypyc/irbuild/main.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
"""Transform a mypy AST to the IR form (Intermediate Representation).
|
||||
|
||||
For example, consider a function like this:
|
||||
|
||||
def f(x: int) -> int:
|
||||
return x * 2 + 1
|
||||
|
||||
It would be translated to something that conceptually looks like this:
|
||||
|
||||
r0 = 2
|
||||
r1 = 1
|
||||
r2 = x * r0 :: int
|
||||
r3 = r2 + r1 :: int
|
||||
return r3
|
||||
|
||||
This module deals with the module-level IR transformation logic and
|
||||
putting it all together. The actual IR is implemented in mypyc.ir.
|
||||
|
||||
For the core of the IR transform implementation, look at build_ir()
|
||||
below, mypyc.irbuild.builder, and mypyc.irbuild.visitor.
|
||||
"""
|
||||
|
||||
from mypy.backports import OrderedDict
|
||||
from typing import List, Dict, Callable, Any, TypeVar, cast
|
||||
|
||||
from mypy.nodes import MypyFile, Expression, ClassDef
|
||||
from mypy.types import Type
|
||||
from mypy.state import strict_optional_set
|
||||
from mypy.build import Graph
|
||||
|
||||
from mypyc.common import TOP_LEVEL_NAME
|
||||
from mypyc.errors import Errors
|
||||
from mypyc.options import CompilerOptions
|
||||
from mypyc.ir.rtypes import none_rprimitive
|
||||
from mypyc.ir.module_ir import ModuleIR, ModuleIRs
|
||||
from mypyc.ir.func_ir import FuncIR, FuncDecl, FuncSignature
|
||||
from mypyc.irbuild.prebuildvisitor import PreBuildVisitor
|
||||
from mypyc.irbuild.vtable import compute_vtable
|
||||
from mypyc.irbuild.prepare import build_type_map, find_singledispatch_register_impls
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
from mypyc.irbuild.visitor import IRBuilderVisitor
|
||||
from mypyc.irbuild.mapper import Mapper
|
||||
|
||||
|
||||
# The stubs for callable contextmanagers are busted so cast it to the
|
||||
# right type...
|
||||
F = TypeVar('F', bound=Callable[..., Any])
|
||||
strict_optional_dec = cast(Callable[[F], F], strict_optional_set(True))
|
||||
|
||||
|
||||
@strict_optional_dec # Turn on strict optional for any type manipulations we do
|
||||
def build_ir(modules: List[MypyFile],
|
||||
graph: Graph,
|
||||
types: Dict[Expression, Type],
|
||||
mapper: 'Mapper',
|
||||
options: CompilerOptions,
|
||||
errors: Errors) -> ModuleIRs:
|
||||
"""Build IR for a set of modules that have been type-checked by mypy."""
|
||||
|
||||
build_type_map(mapper, modules, graph, types, options, errors)
|
||||
singledispatch_info = find_singledispatch_register_impls(modules, errors)
|
||||
|
||||
result: ModuleIRs = OrderedDict()
|
||||
|
||||
# Generate IR for all modules.
|
||||
class_irs = []
|
||||
|
||||
for module in modules:
|
||||
# First pass to determine free symbols.
|
||||
pbv = PreBuildVisitor(errors, module, singledispatch_info.decorators_to_remove)
|
||||
module.accept(pbv)
|
||||
|
||||
# Construct and configure builder objects (cyclic runtime dependency).
|
||||
visitor = IRBuilderVisitor()
|
||||
builder = IRBuilder(
|
||||
module.fullname, types, graph, errors, mapper, pbv, visitor, options,
|
||||
singledispatch_info.singledispatch_impls,
|
||||
)
|
||||
visitor.builder = builder
|
||||
|
||||
# Second pass does the bulk of the work.
|
||||
transform_mypy_file(builder, module)
|
||||
module_ir = ModuleIR(
|
||||
module.fullname,
|
||||
list(builder.imports),
|
||||
builder.functions,
|
||||
builder.classes,
|
||||
builder.final_names
|
||||
)
|
||||
result[module.fullname] = module_ir
|
||||
class_irs.extend(builder.classes)
|
||||
|
||||
# Compute vtables.
|
||||
for cir in class_irs:
|
||||
if cir.is_ext_class:
|
||||
compute_vtable(cir)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def transform_mypy_file(builder: IRBuilder, mypyfile: MypyFile) -> None:
|
||||
"""Generate IR for a single module."""
|
||||
|
||||
if mypyfile.fullname in ('typing', 'abc'):
|
||||
# These module are special; their contents are currently all
|
||||
# built-in primitives.
|
||||
return
|
||||
|
||||
builder.set_module(mypyfile.fullname, mypyfile.path)
|
||||
|
||||
classes = [node for node in mypyfile.defs if isinstance(node, ClassDef)]
|
||||
|
||||
# Collect all classes.
|
||||
for cls in classes:
|
||||
ir = builder.mapper.type_to_ir[cls.info]
|
||||
builder.classes.append(ir)
|
||||
|
||||
builder.enter('<top level>')
|
||||
|
||||
# Make sure we have a builtins import
|
||||
builder.gen_import('builtins', -1)
|
||||
|
||||
# Generate ops.
|
||||
for node in mypyfile.defs:
|
||||
builder.accept(node)
|
||||
|
||||
builder.maybe_add_implicit_return()
|
||||
|
||||
# Generate special function representing module top level.
|
||||
args, _, blocks, ret_type, _ = builder.leave()
|
||||
sig = FuncSignature([], none_rprimitive)
|
||||
func_ir = FuncIR(FuncDecl(TOP_LEVEL_NAME, None, builder.module_name, sig), args, blocks,
|
||||
traceback_name="<module>")
|
||||
builder.functions.append(func_ir)
|
||||
Binary file not shown.
162
.venv/lib/python3.8/site-packages/mypyc/irbuild/mapper.py
Normal file
162
.venv/lib/python3.8/site-packages/mypyc/irbuild/mapper.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"""Maintain a mapping from mypy concepts to IR/compiled concepts."""
|
||||
|
||||
from typing import Dict, Optional
|
||||
|
||||
from mypy.nodes import FuncDef, TypeInfo, SymbolNode, ArgKind, ARG_STAR, ARG_STAR2
|
||||
from mypy.types import (
|
||||
Instance, Type, CallableType, LiteralType, TypedDictType, UnboundType, PartialType,
|
||||
UninhabitedType, Overloaded, UnionType, TypeType, AnyType, NoneTyp, TupleType, TypeVarType,
|
||||
get_proper_type
|
||||
)
|
||||
|
||||
from mypyc.ir.rtypes import (
|
||||
RType, RUnion, RTuple, RInstance, object_rprimitive, dict_rprimitive, tuple_rprimitive,
|
||||
none_rprimitive, int_rprimitive, float_rprimitive, str_rprimitive, bool_rprimitive,
|
||||
list_rprimitive, set_rprimitive, range_rprimitive, bytes_rprimitive
|
||||
)
|
||||
from mypyc.ir.func_ir import FuncSignature, FuncDecl, RuntimeArg
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
|
||||
|
||||
class Mapper:
|
||||
"""Keep track of mappings from mypy concepts to IR concepts.
|
||||
|
||||
For example, we keep track of how the mypy TypeInfos of compiled
|
||||
classes map to class IR objects.
|
||||
|
||||
This state is shared across all modules being compiled in all
|
||||
compilation groups.
|
||||
"""
|
||||
|
||||
def __init__(self, group_map: Dict[str, Optional[str]]) -> None:
|
||||
self.group_map = group_map
|
||||
self.type_to_ir: Dict[TypeInfo, ClassIR] = {}
|
||||
self.func_to_decl: Dict[SymbolNode, FuncDecl] = {}
|
||||
|
||||
def type_to_rtype(self, typ: Optional[Type]) -> RType:
|
||||
if typ is None:
|
||||
return object_rprimitive
|
||||
|
||||
typ = get_proper_type(typ)
|
||||
if isinstance(typ, Instance):
|
||||
if typ.type.fullname == 'builtins.int':
|
||||
return int_rprimitive
|
||||
elif typ.type.fullname == 'builtins.float':
|
||||
return float_rprimitive
|
||||
elif typ.type.fullname == 'builtins.bool':
|
||||
return bool_rprimitive
|
||||
elif typ.type.fullname == 'builtins.str':
|
||||
return str_rprimitive
|
||||
elif typ.type.fullname == 'builtins.bytes':
|
||||
return bytes_rprimitive
|
||||
elif typ.type.fullname == 'builtins.list':
|
||||
return list_rprimitive
|
||||
# Dict subclasses are at least somewhat common and we
|
||||
# specifically support them, so make sure that dict operations
|
||||
# get optimized on them.
|
||||
elif any(cls.fullname == 'builtins.dict' for cls in typ.type.mro):
|
||||
return dict_rprimitive
|
||||
elif typ.type.fullname == 'builtins.set':
|
||||
return set_rprimitive
|
||||
elif typ.type.fullname == 'builtins.tuple':
|
||||
return tuple_rprimitive # Varying-length tuple
|
||||
elif typ.type.fullname == 'builtins.range':
|
||||
return range_rprimitive
|
||||
elif typ.type in self.type_to_ir:
|
||||
inst = RInstance(self.type_to_ir[typ.type])
|
||||
# Treat protocols as Union[protocol, object], so that we can do fast
|
||||
# method calls in the cases where the protocol is explicitly inherited from
|
||||
# and fall back to generic operations when it isn't.
|
||||
if typ.type.is_protocol:
|
||||
return RUnion([inst, object_rprimitive])
|
||||
else:
|
||||
return inst
|
||||
else:
|
||||
return object_rprimitive
|
||||
elif isinstance(typ, TupleType):
|
||||
# Use our unboxed tuples for raw tuples but fall back to
|
||||
# being boxed for NamedTuple.
|
||||
if typ.partial_fallback.type.fullname == 'builtins.tuple':
|
||||
return RTuple([self.type_to_rtype(t) for t in typ.items])
|
||||
else:
|
||||
return tuple_rprimitive
|
||||
elif isinstance(typ, CallableType):
|
||||
return object_rprimitive
|
||||
elif isinstance(typ, NoneTyp):
|
||||
return none_rprimitive
|
||||
elif isinstance(typ, UnionType):
|
||||
return RUnion([self.type_to_rtype(item)
|
||||
for item in typ.items])
|
||||
elif isinstance(typ, AnyType):
|
||||
return object_rprimitive
|
||||
elif isinstance(typ, TypeType):
|
||||
return object_rprimitive
|
||||
elif isinstance(typ, TypeVarType):
|
||||
# Erase type variable to upper bound.
|
||||
# TODO: Erase to union if object has value restriction?
|
||||
return self.type_to_rtype(typ.upper_bound)
|
||||
elif isinstance(typ, PartialType):
|
||||
assert typ.var.type is not None
|
||||
return self.type_to_rtype(typ.var.type)
|
||||
elif isinstance(typ, Overloaded):
|
||||
return object_rprimitive
|
||||
elif isinstance(typ, TypedDictType):
|
||||
return dict_rprimitive
|
||||
elif isinstance(typ, LiteralType):
|
||||
return self.type_to_rtype(typ.fallback)
|
||||
elif isinstance(typ, (UninhabitedType, UnboundType)):
|
||||
# Sure, whatever!
|
||||
return object_rprimitive
|
||||
|
||||
# I think we've covered everything that is supposed to
|
||||
# actually show up, so anything else is a bug somewhere.
|
||||
assert False, 'unexpected type %s' % type(typ)
|
||||
|
||||
def get_arg_rtype(self, typ: Type, kind: ArgKind) -> RType:
|
||||
if kind == ARG_STAR:
|
||||
return tuple_rprimitive
|
||||
elif kind == ARG_STAR2:
|
||||
return dict_rprimitive
|
||||
else:
|
||||
return self.type_to_rtype(typ)
|
||||
|
||||
def fdef_to_sig(self, fdef: FuncDef) -> FuncSignature:
|
||||
if isinstance(fdef.type, CallableType):
|
||||
arg_types = [self.get_arg_rtype(typ, kind)
|
||||
for typ, kind in zip(fdef.type.arg_types, fdef.type.arg_kinds)]
|
||||
arg_pos_onlys = [name is None for name in fdef.type.arg_names]
|
||||
ret = self.type_to_rtype(fdef.type.ret_type)
|
||||
else:
|
||||
# Handle unannotated functions
|
||||
arg_types = [object_rprimitive for arg in fdef.arguments]
|
||||
arg_pos_onlys = [arg.pos_only for arg in fdef.arguments]
|
||||
# We at least know the return type for __init__ methods will be None.
|
||||
is_init_method = fdef.name == '__init__' and bool(fdef.info)
|
||||
if is_init_method:
|
||||
ret = none_rprimitive
|
||||
else:
|
||||
ret = object_rprimitive
|
||||
|
||||
# mypyc FuncSignatures (unlike mypy types) want to have a name
|
||||
# present even when the argument is position only, since it is
|
||||
# the sole way that FuncDecl arguments are tracked. This is
|
||||
# generally fine except in some cases (like for computing
|
||||
# init_sig) we need to produce FuncSignatures from a
|
||||
# deserialized FuncDef that lacks arguments. We won't ever
|
||||
# need to use those inside of a FuncIR, so we just make up
|
||||
# some crap.
|
||||
if hasattr(fdef, 'arguments'):
|
||||
arg_names = [arg.variable.name for arg in fdef.arguments]
|
||||
else:
|
||||
arg_names = [name or '' for name in fdef.arg_names]
|
||||
|
||||
args = [RuntimeArg(arg_name, arg_type, arg_kind, arg_pos_only)
|
||||
for arg_name, arg_kind, arg_type, arg_pos_only
|
||||
in zip(arg_names, fdef.arg_kinds, arg_types, arg_pos_onlys)]
|
||||
|
||||
# We force certain dunder methods to return objects to support letting them
|
||||
# return NotImplemented. It also avoids some pointless boxing and unboxing,
|
||||
# since tp_richcompare needs an object anyways.
|
||||
if fdef.name in ('__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__'):
|
||||
ret = object_rprimitive
|
||||
return FuncSignature(args, ret)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,184 @@
|
|||
"""Helpers for dealing with nonlocal control such as 'break' and 'return'.
|
||||
|
||||
Model how these behave differently in different contexts.
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import Optional, Union
|
||||
from typing_extensions import TYPE_CHECKING
|
||||
|
||||
from mypyc.ir.ops import (
|
||||
Branch, BasicBlock, Unreachable, Value, Goto, Integer, Assign, Register, Return,
|
||||
NO_TRACEBACK_LINE_NO
|
||||
)
|
||||
from mypyc.primitives.exc_ops import set_stop_iteration_value, restore_exc_info_op
|
||||
from mypyc.irbuild.targets import AssignmentTarget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
|
||||
|
||||
class NonlocalControl:
|
||||
"""ABC representing a stack frame of constructs that modify nonlocal control flow.
|
||||
|
||||
The nonlocal control flow constructs are break, continue, and
|
||||
return, and their behavior is modified by a number of other
|
||||
constructs. The most obvious is loop, which override where break
|
||||
and continue jump to, but also `except` (which needs to clear
|
||||
exc_info when left) and (eventually) finally blocks (which need to
|
||||
ensure that the finally block is always executed when leaving the
|
||||
try/except blocks).
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def gen_break(self, builder: 'IRBuilder', line: int) -> None: pass
|
||||
|
||||
@abstractmethod
|
||||
def gen_continue(self, builder: 'IRBuilder', line: int) -> None: pass
|
||||
|
||||
@abstractmethod
|
||||
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None: pass
|
||||
|
||||
|
||||
class BaseNonlocalControl(NonlocalControl):
|
||||
"""Default nonlocal control outside any statements that affect it."""
|
||||
|
||||
def gen_break(self, builder: 'IRBuilder', line: int) -> None:
|
||||
assert False, "break outside of loop"
|
||||
|
||||
def gen_continue(self, builder: 'IRBuilder', line: int) -> None:
|
||||
assert False, "continue outside of loop"
|
||||
|
||||
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None:
|
||||
builder.add(Return(value))
|
||||
|
||||
|
||||
class LoopNonlocalControl(NonlocalControl):
|
||||
"""Nonlocal control within a loop."""
|
||||
|
||||
def __init__(self,
|
||||
outer: NonlocalControl,
|
||||
continue_block: BasicBlock,
|
||||
break_block: BasicBlock) -> None:
|
||||
self.outer = outer
|
||||
self.continue_block = continue_block
|
||||
self.break_block = break_block
|
||||
|
||||
def gen_break(self, builder: 'IRBuilder', line: int) -> None:
|
||||
builder.add(Goto(self.break_block))
|
||||
|
||||
def gen_continue(self, builder: 'IRBuilder', line: int) -> None:
|
||||
builder.add(Goto(self.continue_block))
|
||||
|
||||
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None:
|
||||
self.outer.gen_return(builder, value, line)
|
||||
|
||||
|
||||
class GeneratorNonlocalControl(BaseNonlocalControl):
|
||||
"""Default nonlocal control in a generator function outside statements."""
|
||||
|
||||
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None:
|
||||
# Assign an invalid next label number so that the next time
|
||||
# __next__ is called, we jump to the case in which
|
||||
# StopIteration is raised.
|
||||
builder.assign(builder.fn_info.generator_class.next_label_target,
|
||||
Integer(-1),
|
||||
line)
|
||||
|
||||
# Raise a StopIteration containing a field for the value that
|
||||
# should be returned. Before doing so, create a new block
|
||||
# without an error handler set so that the implicitly thrown
|
||||
# StopIteration isn't caught by except blocks inside of the
|
||||
# generator function.
|
||||
builder.builder.push_error_handler(None)
|
||||
builder.goto_and_activate(BasicBlock())
|
||||
|
||||
# Skip creating a traceback frame when we raise here, because
|
||||
# we don't care about the traceback frame and it is kind of
|
||||
# expensive since raising StopIteration is an extremely common
|
||||
# case. Also we call a special internal function to set
|
||||
# StopIteration instead of using RaiseStandardError because
|
||||
# the obvious thing doesn't work if the value is a tuple
|
||||
# (???).
|
||||
builder.call_c(set_stop_iteration_value, [value], NO_TRACEBACK_LINE_NO)
|
||||
builder.add(Unreachable())
|
||||
builder.builder.pop_error_handler()
|
||||
|
||||
|
||||
class CleanupNonlocalControl(NonlocalControl):
|
||||
"""Abstract nonlocal control that runs some cleanup code. """
|
||||
|
||||
def __init__(self, outer: NonlocalControl) -> None:
|
||||
self.outer = outer
|
||||
|
||||
@abstractmethod
|
||||
def gen_cleanup(self, builder: 'IRBuilder', line: int) -> None: ...
|
||||
|
||||
def gen_break(self, builder: 'IRBuilder', line: int) -> None:
|
||||
self.gen_cleanup(builder, line)
|
||||
self.outer.gen_break(builder, line)
|
||||
|
||||
def gen_continue(self, builder: 'IRBuilder', line: int) -> None:
|
||||
self.gen_cleanup(builder, line)
|
||||
self.outer.gen_continue(builder, line)
|
||||
|
||||
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None:
|
||||
self.gen_cleanup(builder, line)
|
||||
self.outer.gen_return(builder, value, line)
|
||||
|
||||
|
||||
class TryFinallyNonlocalControl(NonlocalControl):
|
||||
"""Nonlocal control within try/finally."""
|
||||
|
||||
def __init__(self, target: BasicBlock) -> None:
|
||||
self.target = target
|
||||
self.ret_reg: Optional[Register] = None
|
||||
|
||||
def gen_break(self, builder: 'IRBuilder', line: int) -> None:
|
||||
builder.error("break inside try/finally block is unimplemented", line)
|
||||
|
||||
def gen_continue(self, builder: 'IRBuilder', line: int) -> None:
|
||||
builder.error("continue inside try/finally block is unimplemented", line)
|
||||
|
||||
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None:
|
||||
if self.ret_reg is None:
|
||||
self.ret_reg = Register(builder.ret_types[-1])
|
||||
|
||||
builder.add(Assign(self.ret_reg, value))
|
||||
builder.add(Goto(self.target))
|
||||
|
||||
|
||||
class ExceptNonlocalControl(CleanupNonlocalControl):
|
||||
"""Nonlocal control for except blocks.
|
||||
|
||||
Just makes sure that sys.exc_info always gets restored when we leave.
|
||||
This is super annoying.
|
||||
"""
|
||||
|
||||
def __init__(self, outer: NonlocalControl, saved: Union[Value, AssignmentTarget]) -> None:
|
||||
super().__init__(outer)
|
||||
self.saved = saved
|
||||
|
||||
def gen_cleanup(self, builder: 'IRBuilder', line: int) -> None:
|
||||
builder.call_c(restore_exc_info_op, [builder.read(self.saved)], line)
|
||||
|
||||
|
||||
class FinallyNonlocalControl(CleanupNonlocalControl):
|
||||
"""Nonlocal control for finally blocks.
|
||||
|
||||
Just makes sure that sys.exc_info always gets restored when we
|
||||
leave and the return register is decrefed if it isn't null.
|
||||
"""
|
||||
|
||||
def __init__(self, outer: NonlocalControl, ret_reg: Optional[Value], saved: Value) -> None:
|
||||
super().__init__(outer)
|
||||
self.ret_reg = ret_reg
|
||||
self.saved = saved
|
||||
|
||||
def gen_cleanup(self, builder: 'IRBuilder', line: int) -> None:
|
||||
# Restore the old exc_info
|
||||
target, cleanup = BasicBlock(), BasicBlock()
|
||||
builder.add(Branch(self.saved, target, cleanup, Branch.IS_ERROR))
|
||||
builder.activate_block(cleanup)
|
||||
builder.call_c(restore_exc_info_op, [self.saved], line)
|
||||
builder.goto_and_activate(target)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,168 @@
|
|||
from mypyc.errors import Errors
|
||||
from typing import Dict, List, Set
|
||||
|
||||
from mypy.nodes import (
|
||||
Decorator, Expression, FuncDef, FuncItem, LambdaExpr, NameExpr, SymbolNode, Var, MemberExpr,
|
||||
MypyFile
|
||||
)
|
||||
from mypy.traverser import TraverserVisitor
|
||||
|
||||
|
||||
class PreBuildVisitor(TraverserVisitor):
|
||||
"""Mypy file AST visitor run before building the IR.
|
||||
|
||||
This collects various things, including:
|
||||
|
||||
* Determine relationships between nested functions and functions that
|
||||
contain nested functions
|
||||
* Find non-local variables (free variables)
|
||||
* Find property setters
|
||||
* Find decorators of functions
|
||||
|
||||
The main IR build pass uses this information.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
errors: Errors,
|
||||
current_file: MypyFile,
|
||||
decorators_to_remove: Dict[FuncDef, List[int]],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
# Dict from a function to symbols defined directly in the
|
||||
# function that are used as non-local (free) variables within a
|
||||
# nested function.
|
||||
self.free_variables: Dict[FuncItem, Set[SymbolNode]] = {}
|
||||
|
||||
# Intermediate data structure used to find the function where
|
||||
# a SymbolNode is declared. Initially this may point to a
|
||||
# function nested inside the function with the declaration,
|
||||
# but we'll eventually update this to refer to the function
|
||||
# with the declaration.
|
||||
self.symbols_to_funcs: Dict[SymbolNode, FuncItem] = {}
|
||||
|
||||
# Stack representing current function nesting.
|
||||
self.funcs: List[FuncItem] = []
|
||||
|
||||
# All property setters encountered so far.
|
||||
self.prop_setters: Set[FuncDef] = set()
|
||||
|
||||
# A map from any function that contains nested functions to
|
||||
# a set of all the functions that are nested within it.
|
||||
self.encapsulating_funcs: Dict[FuncItem, List[FuncItem]] = {}
|
||||
|
||||
# Map nested function to its parent/encapsulating function.
|
||||
self.nested_funcs: Dict[FuncItem, FuncItem] = {}
|
||||
|
||||
# Map function to its non-special decorators.
|
||||
self.funcs_to_decorators: Dict[FuncDef, List[Expression]] = {}
|
||||
|
||||
# Map function to indices of decorators to remove
|
||||
self.decorators_to_remove: Dict[FuncDef, List[int]] = decorators_to_remove
|
||||
|
||||
self.errors: Errors = errors
|
||||
|
||||
self.current_file: MypyFile = current_file
|
||||
|
||||
def visit_decorator(self, dec: Decorator) -> None:
|
||||
if dec.decorators:
|
||||
# Only add the function being decorated if there exist
|
||||
# (ordinary) decorators in the decorator list. Certain
|
||||
# decorators (such as @property, @abstractmethod) are
|
||||
# special cased and removed from this list by
|
||||
# mypy. Functions decorated only by special decorators
|
||||
# (and property setters) are not treated as decorated
|
||||
# functions by the IR builder.
|
||||
if isinstance(dec.decorators[0], MemberExpr) and dec.decorators[0].name == 'setter':
|
||||
# Property setters are not treated as decorated methods.
|
||||
self.prop_setters.add(dec.func)
|
||||
else:
|
||||
decorators_to_store = dec.decorators.copy()
|
||||
if dec.func in self.decorators_to_remove:
|
||||
to_remove = self.decorators_to_remove[dec.func]
|
||||
|
||||
for i in reversed(to_remove):
|
||||
del decorators_to_store[i]
|
||||
# if all of the decorators are removed, we shouldn't treat this as a decorated
|
||||
# function because there aren't any decorators to apply
|
||||
if not decorators_to_store:
|
||||
return
|
||||
|
||||
self.funcs_to_decorators[dec.func] = decorators_to_store
|
||||
super().visit_decorator(dec)
|
||||
|
||||
def visit_func_def(self, fdef: FuncItem) -> None:
|
||||
# TODO: What about overloaded functions?
|
||||
self.visit_func(fdef)
|
||||
|
||||
def visit_lambda_expr(self, expr: LambdaExpr) -> None:
|
||||
self.visit_func(expr)
|
||||
|
||||
def visit_func(self, func: FuncItem) -> None:
|
||||
# If there were already functions or lambda expressions
|
||||
# defined in the function stack, then note the previous
|
||||
# FuncItem as containing a nested function and the current
|
||||
# FuncItem as being a nested function.
|
||||
if self.funcs:
|
||||
# Add the new func to the set of nested funcs within the
|
||||
# func at top of the func stack.
|
||||
self.encapsulating_funcs.setdefault(self.funcs[-1], []).append(func)
|
||||
# Add the func at top of the func stack as the parent of
|
||||
# new func.
|
||||
self.nested_funcs[func] = self.funcs[-1]
|
||||
|
||||
self.funcs.append(func)
|
||||
super().visit_func(func)
|
||||
self.funcs.pop()
|
||||
|
||||
def visit_name_expr(self, expr: NameExpr) -> None:
|
||||
if isinstance(expr.node, (Var, FuncDef)):
|
||||
self.visit_symbol_node(expr.node)
|
||||
|
||||
def visit_var(self, var: Var) -> None:
|
||||
self.visit_symbol_node(var)
|
||||
|
||||
def visit_symbol_node(self, symbol: SymbolNode) -> None:
|
||||
if not self.funcs:
|
||||
# We are not inside a function and hence do not need to do
|
||||
# anything regarding free variables.
|
||||
return
|
||||
|
||||
if symbol in self.symbols_to_funcs:
|
||||
orig_func = self.symbols_to_funcs[symbol]
|
||||
if self.is_parent(self.funcs[-1], orig_func):
|
||||
# The function in which the symbol was previously seen is
|
||||
# nested within the function currently being visited. Thus
|
||||
# the current function is a better candidate to contain the
|
||||
# declaration.
|
||||
self.symbols_to_funcs[symbol] = self.funcs[-1]
|
||||
# TODO: Remove from the orig_func free_variables set?
|
||||
self.free_variables.setdefault(self.funcs[-1], set()).add(symbol)
|
||||
|
||||
elif self.is_parent(orig_func, self.funcs[-1]):
|
||||
# The SymbolNode instance has already been visited
|
||||
# before in a parent function, thus it's a non-local
|
||||
# symbol.
|
||||
self.add_free_variable(symbol)
|
||||
|
||||
else:
|
||||
# This is the first time the SymbolNode is being
|
||||
# visited. We map the SymbolNode to the current FuncDef
|
||||
# being visited to note where it was first visited.
|
||||
self.symbols_to_funcs[symbol] = self.funcs[-1]
|
||||
|
||||
def is_parent(self, fitem: FuncItem, child: FuncItem) -> bool:
|
||||
# Check if child is nested within fdef (possibly indirectly
|
||||
# within multiple nested functions).
|
||||
if child in self.nested_funcs:
|
||||
parent = self.nested_funcs[child]
|
||||
if parent == fitem:
|
||||
return True
|
||||
return self.is_parent(fitem, parent)
|
||||
return False
|
||||
|
||||
def add_free_variable(self, symbol: SymbolNode) -> None:
|
||||
# Find the function where the symbol was (likely) first declared,
|
||||
# and mark is as a non-local symbol within that function.
|
||||
func = self.symbols_to_funcs[symbol]
|
||||
self.free_variables.setdefault(func, set()).add(symbol)
|
||||
Binary file not shown.
425
.venv/lib/python3.8/site-packages/mypyc/irbuild/prepare.py
Normal file
425
.venv/lib/python3.8/site-packages/mypyc/irbuild/prepare.py
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
"""Prepare for IR transform.
|
||||
|
||||
This needs to run after type checking and before generating IR.
|
||||
|
||||
For example, construct partially initialized FuncIR and ClassIR
|
||||
objects for all functions and classes. This allows us to bind
|
||||
references to functions and classes before we've generated full IR for
|
||||
functions or classes. The actual IR transform will then populate all
|
||||
the missing bits, such as function bodies (basic blocks).
|
||||
|
||||
Also build a mapping from mypy TypeInfos to ClassIR objects.
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Iterable, Optional, Union, DefaultDict, NamedTuple, Tuple
|
||||
|
||||
from mypy.nodes import (
|
||||
ClassDef, OverloadedFuncDef, Var,
|
||||
SymbolNode, ARG_STAR, ARG_STAR2, CallExpr, Decorator, Expression, FuncDef,
|
||||
MemberExpr, MypyFile, NameExpr, RefExpr, TypeInfo
|
||||
)
|
||||
from mypy.types import Type, Instance, get_proper_type
|
||||
from mypy.build import Graph
|
||||
|
||||
from mypyc.ir.ops import DeserMaps
|
||||
from mypyc.ir.rtypes import RInstance, tuple_rprimitive, dict_rprimitive
|
||||
from mypyc.ir.func_ir import (
|
||||
FuncDecl, FuncSignature, RuntimeArg, FUNC_NORMAL, FUNC_STATICMETHOD, FUNC_CLASSMETHOD
|
||||
)
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.common import PROPSET_PREFIX, get_id_from_name
|
||||
from mypyc.irbuild.mapper import Mapper
|
||||
from mypyc.irbuild.util import (
|
||||
get_func_def, is_dataclass, is_trait, is_extension_class, get_mypyc_attrs
|
||||
)
|
||||
from mypyc.errors import Errors
|
||||
from mypyc.options import CompilerOptions
|
||||
from mypyc.crash import catch_errors
|
||||
from collections import defaultdict
|
||||
from mypy.traverser import TraverserVisitor
|
||||
from mypy.semanal import refers_to_fullname
|
||||
|
||||
|
||||
def build_type_map(mapper: Mapper,
|
||||
modules: List[MypyFile],
|
||||
graph: Graph,
|
||||
types: Dict[Expression, Type],
|
||||
options: CompilerOptions,
|
||||
errors: Errors) -> None:
|
||||
# Collect all classes defined in everything we are compiling
|
||||
classes = []
|
||||
for module in modules:
|
||||
module_classes = [node for node in module.defs if isinstance(node, ClassDef)]
|
||||
classes.extend([(module, cdef) for cdef in module_classes])
|
||||
|
||||
# Collect all class mappings so that we can bind arbitrary class name
|
||||
# references even if there are import cycles.
|
||||
for module, cdef in classes:
|
||||
class_ir = ClassIR(cdef.name, module.fullname, is_trait(cdef),
|
||||
is_abstract=cdef.info.is_abstract)
|
||||
class_ir.is_ext_class = is_extension_class(cdef)
|
||||
if class_ir.is_ext_class:
|
||||
class_ir.deletable = cdef.info.deletable_attributes[:]
|
||||
# If global optimizations are disabled, turn of tracking of class children
|
||||
if not options.global_opts:
|
||||
class_ir.children = None
|
||||
mapper.type_to_ir[cdef.info] = class_ir
|
||||
|
||||
# Populate structural information in class IR for extension classes.
|
||||
for module, cdef in classes:
|
||||
with catch_errors(module.path, cdef.line):
|
||||
if mapper.type_to_ir[cdef.info].is_ext_class:
|
||||
prepare_class_def(module.path, module.fullname, cdef, errors, mapper)
|
||||
else:
|
||||
prepare_non_ext_class_def(module.path, module.fullname, cdef, errors, mapper)
|
||||
|
||||
# Collect all the functions also. We collect from the symbol table
|
||||
# so that we can easily pick out the right copy of a function that
|
||||
# is conditionally defined.
|
||||
for module in modules:
|
||||
for func in get_module_func_defs(module):
|
||||
prepare_func_def(module.fullname, None, func, mapper)
|
||||
# TODO: what else?
|
||||
|
||||
|
||||
def is_from_module(node: SymbolNode, module: MypyFile) -> bool:
|
||||
return node.fullname == module.fullname + '.' + node.name
|
||||
|
||||
|
||||
def load_type_map(mapper: 'Mapper',
|
||||
modules: List[MypyFile],
|
||||
deser_ctx: DeserMaps) -> None:
|
||||
"""Populate a Mapper with deserialized IR from a list of modules."""
|
||||
for module in modules:
|
||||
for name, node in module.names.items():
|
||||
if isinstance(node.node, TypeInfo) and is_from_module(node.node, module):
|
||||
ir = deser_ctx.classes[node.node.fullname]
|
||||
mapper.type_to_ir[node.node] = ir
|
||||
mapper.func_to_decl[node.node] = ir.ctor
|
||||
|
||||
for module in modules:
|
||||
for func in get_module_func_defs(module):
|
||||
func_id = get_id_from_name(func.name, func.fullname, func.line)
|
||||
mapper.func_to_decl[func] = deser_ctx.functions[func_id].decl
|
||||
|
||||
|
||||
def get_module_func_defs(module: MypyFile) -> Iterable[FuncDef]:
|
||||
"""Collect all of the (non-method) functions declared in a module."""
|
||||
for name, node in module.names.items():
|
||||
# We need to filter out functions that are imported or
|
||||
# aliases. The best way to do this seems to be by
|
||||
# checking that the fullname matches.
|
||||
if (isinstance(node.node, (FuncDef, Decorator, OverloadedFuncDef))
|
||||
and is_from_module(node.node, module)):
|
||||
yield get_func_def(node.node)
|
||||
|
||||
|
||||
def prepare_func_def(module_name: str, class_name: Optional[str],
|
||||
fdef: FuncDef, mapper: Mapper) -> FuncDecl:
|
||||
kind = FUNC_STATICMETHOD if fdef.is_static else (
|
||||
FUNC_CLASSMETHOD if fdef.is_class else FUNC_NORMAL)
|
||||
decl = FuncDecl(fdef.name, class_name, module_name, mapper.fdef_to_sig(fdef), kind)
|
||||
mapper.func_to_decl[fdef] = decl
|
||||
return decl
|
||||
|
||||
|
||||
def prepare_method_def(ir: ClassIR, module_name: str, cdef: ClassDef, mapper: Mapper,
|
||||
node: Union[FuncDef, Decorator]) -> None:
|
||||
if isinstance(node, FuncDef):
|
||||
ir.method_decls[node.name] = prepare_func_def(module_name, cdef.name, node, mapper)
|
||||
elif isinstance(node, Decorator):
|
||||
# TODO: do something about abstract methods here. Currently, they are handled just like
|
||||
# normal methods.
|
||||
decl = prepare_func_def(module_name, cdef.name, node.func, mapper)
|
||||
if not node.decorators:
|
||||
ir.method_decls[node.name] = decl
|
||||
elif isinstance(node.decorators[0], MemberExpr) and node.decorators[0].name == 'setter':
|
||||
# Make property setter name different than getter name so there are no
|
||||
# name clashes when generating C code, and property lookup at the IR level
|
||||
# works correctly.
|
||||
decl.name = PROPSET_PREFIX + decl.name
|
||||
decl.is_prop_setter = True
|
||||
ir.method_decls[PROPSET_PREFIX + node.name] = decl
|
||||
|
||||
if node.func.is_property:
|
||||
assert node.func.type, f"Expected return type annotation for property '{node.name}'"
|
||||
decl.is_prop_getter = True
|
||||
ir.property_types[node.name] = decl.sig.ret_type
|
||||
|
||||
|
||||
def is_valid_multipart_property_def(prop: OverloadedFuncDef) -> bool:
|
||||
# Checks to ensure supported property decorator semantics
|
||||
if len(prop.items) == 2:
|
||||
getter = prop.items[0]
|
||||
setter = prop.items[1]
|
||||
if isinstance(getter, Decorator) and isinstance(setter, Decorator):
|
||||
if getter.func.is_property and len(setter.decorators) == 1:
|
||||
if isinstance(setter.decorators[0], MemberExpr):
|
||||
if setter.decorators[0].name == "setter":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def can_subclass_builtin(builtin_base: str) -> bool:
|
||||
# BaseException and dict are special cased.
|
||||
return builtin_base in (
|
||||
('builtins.Exception', 'builtins.LookupError', 'builtins.IndexError',
|
||||
'builtins.Warning', 'builtins.UserWarning', 'builtins.ValueError',
|
||||
'builtins.object', ))
|
||||
|
||||
|
||||
def prepare_class_def(path: str, module_name: str, cdef: ClassDef,
|
||||
errors: Errors, mapper: Mapper) -> None:
|
||||
|
||||
ir = mapper.type_to_ir[cdef.info]
|
||||
info = cdef.info
|
||||
|
||||
attrs = get_mypyc_attrs(cdef)
|
||||
if attrs.get("allow_interpreted_subclasses") is True:
|
||||
ir.allow_interpreted_subclasses = True
|
||||
|
||||
# We sort the table for determinism here on Python 3.5
|
||||
for name, node in sorted(info.names.items()):
|
||||
# Currently all plugin generated methods are dummies and not included.
|
||||
if node.plugin_generated:
|
||||
continue
|
||||
|
||||
if isinstance(node.node, Var):
|
||||
assert node.node.type, "Class member %s missing type" % name
|
||||
if not node.node.is_classvar and name not in ('__slots__', '__deletable__'):
|
||||
ir.attributes[name] = mapper.type_to_rtype(node.node.type)
|
||||
elif isinstance(node.node, (FuncDef, Decorator)):
|
||||
prepare_method_def(ir, module_name, cdef, mapper, node.node)
|
||||
elif isinstance(node.node, OverloadedFuncDef):
|
||||
# Handle case for property with both a getter and a setter
|
||||
if node.node.is_property:
|
||||
if is_valid_multipart_property_def(node.node):
|
||||
for item in node.node.items:
|
||||
prepare_method_def(ir, module_name, cdef, mapper, item)
|
||||
else:
|
||||
errors.error("Unsupported property decorator semantics", path, cdef.line)
|
||||
|
||||
# Handle case for regular function overload
|
||||
else:
|
||||
assert node.node.impl
|
||||
prepare_method_def(ir, module_name, cdef, mapper, node.node.impl)
|
||||
|
||||
# Check for subclassing from builtin types
|
||||
for cls in info.mro:
|
||||
# Special case exceptions and dicts
|
||||
# XXX: How do we handle *other* things??
|
||||
if cls.fullname == 'builtins.BaseException':
|
||||
ir.builtin_base = 'PyBaseExceptionObject'
|
||||
elif cls.fullname == 'builtins.dict':
|
||||
ir.builtin_base = 'PyDictObject'
|
||||
elif cls.fullname.startswith('builtins.'):
|
||||
if not can_subclass_builtin(cls.fullname):
|
||||
# Note that if we try to subclass a C extension class that
|
||||
# isn't in builtins, bad things will happen and we won't
|
||||
# catch it here! But this should catch a lot of the most
|
||||
# common pitfalls.
|
||||
errors.error("Inheriting from most builtin types is unimplemented",
|
||||
path, cdef.line)
|
||||
|
||||
if ir.builtin_base:
|
||||
ir.attributes.clear()
|
||||
|
||||
# Set up a constructor decl
|
||||
init_node = cdef.info['__init__'].node
|
||||
if not ir.is_trait and not ir.builtin_base and isinstance(init_node, FuncDef):
|
||||
init_sig = mapper.fdef_to_sig(init_node)
|
||||
|
||||
defining_ir = mapper.type_to_ir.get(init_node.info)
|
||||
# If there is a nontrivial __init__ that wasn't defined in an
|
||||
# extension class, we need to make the constructor take *args,
|
||||
# **kwargs so it can call tp_init.
|
||||
if ((defining_ir is None or not defining_ir.is_ext_class
|
||||
or cdef.info['__init__'].plugin_generated)
|
||||
and init_node.info.fullname != 'builtins.object'):
|
||||
init_sig = FuncSignature(
|
||||
[init_sig.args[0],
|
||||
RuntimeArg("args", tuple_rprimitive, ARG_STAR),
|
||||
RuntimeArg("kwargs", dict_rprimitive, ARG_STAR2)],
|
||||
init_sig.ret_type)
|
||||
|
||||
ctor_sig = FuncSignature(init_sig.args[1:], RInstance(ir))
|
||||
ir.ctor = FuncDecl(cdef.name, None, module_name, ctor_sig)
|
||||
mapper.func_to_decl[cdef.info] = ir.ctor
|
||||
|
||||
# Set up the parent class
|
||||
bases = [mapper.type_to_ir[base.type] for base in info.bases
|
||||
if base.type in mapper.type_to_ir]
|
||||
if not all(c.is_trait for c in bases[1:]):
|
||||
errors.error("Non-trait bases must appear first in parent list", path, cdef.line)
|
||||
ir.traits = [c for c in bases if c.is_trait]
|
||||
|
||||
mro = []
|
||||
base_mro = []
|
||||
for cls in info.mro:
|
||||
if cls not in mapper.type_to_ir:
|
||||
if cls.fullname != 'builtins.object':
|
||||
ir.inherits_python = True
|
||||
continue
|
||||
base_ir = mapper.type_to_ir[cls]
|
||||
if not base_ir.is_trait:
|
||||
base_mro.append(base_ir)
|
||||
mro.append(base_ir)
|
||||
|
||||
if cls.defn.removed_base_type_exprs or not base_ir.is_ext_class:
|
||||
ir.inherits_python = True
|
||||
|
||||
base_idx = 1 if not ir.is_trait else 0
|
||||
if len(base_mro) > base_idx:
|
||||
ir.base = base_mro[base_idx]
|
||||
ir.mro = mro
|
||||
ir.base_mro = base_mro
|
||||
|
||||
for base in bases:
|
||||
if base.children is not None:
|
||||
base.children.append(ir)
|
||||
|
||||
if is_dataclass(cdef):
|
||||
ir.is_augmented = True
|
||||
|
||||
|
||||
def prepare_non_ext_class_def(path: str, module_name: str, cdef: ClassDef,
|
||||
errors: Errors, mapper: Mapper) -> None:
|
||||
|
||||
ir = mapper.type_to_ir[cdef.info]
|
||||
info = cdef.info
|
||||
|
||||
for name, node in info.names.items():
|
||||
if isinstance(node.node, (FuncDef, Decorator)):
|
||||
prepare_method_def(ir, module_name, cdef, mapper, node.node)
|
||||
elif isinstance(node.node, OverloadedFuncDef):
|
||||
# Handle case for property with both a getter and a setter
|
||||
if node.node.is_property:
|
||||
if not is_valid_multipart_property_def(node.node):
|
||||
errors.error("Unsupported property decorator semantics", path, cdef.line)
|
||||
for item in node.node.items:
|
||||
prepare_method_def(ir, module_name, cdef, mapper, item)
|
||||
# Handle case for regular function overload
|
||||
else:
|
||||
prepare_method_def(ir, module_name, cdef, mapper, get_func_def(node.node))
|
||||
|
||||
if any(
|
||||
cls in mapper.type_to_ir and mapper.type_to_ir[cls].is_ext_class for cls in info.mro
|
||||
):
|
||||
errors.error(
|
||||
"Non-extension classes may not inherit from extension classes", path, cdef.line)
|
||||
|
||||
|
||||
RegisterImplInfo = Tuple[TypeInfo, FuncDef]
|
||||
|
||||
|
||||
class SingledispatchInfo(NamedTuple):
|
||||
singledispatch_impls: Dict[FuncDef, List[RegisterImplInfo]]
|
||||
decorators_to_remove: Dict[FuncDef, List[int]]
|
||||
|
||||
|
||||
def find_singledispatch_register_impls(
|
||||
modules: List[MypyFile],
|
||||
errors: Errors,
|
||||
) -> SingledispatchInfo:
|
||||
visitor = SingledispatchVisitor(errors)
|
||||
for module in modules:
|
||||
visitor.current_path = module.path
|
||||
module.accept(visitor)
|
||||
return SingledispatchInfo(visitor.singledispatch_impls, visitor.decorators_to_remove)
|
||||
|
||||
|
||||
class SingledispatchVisitor(TraverserVisitor):
|
||||
current_path: str
|
||||
|
||||
def __init__(self, errors: Errors) -> None:
|
||||
super().__init__()
|
||||
|
||||
# Map of main singledispatch function to list of registered implementations
|
||||
self.singledispatch_impls: DefaultDict[FuncDef, List[RegisterImplInfo]] = defaultdict(list)
|
||||
|
||||
# Map of decorated function to the indices of any decorators to remove
|
||||
self.decorators_to_remove: Dict[FuncDef, List[int]] = {}
|
||||
|
||||
self.errors: Errors = errors
|
||||
|
||||
def visit_decorator(self, dec: Decorator) -> None:
|
||||
if dec.decorators:
|
||||
decorators_to_store = dec.decorators.copy()
|
||||
decorators_to_remove: List[int] = []
|
||||
# the index of the last non-register decorator before finding a register decorator
|
||||
# when going through decorators from top to bottom
|
||||
last_non_register: Optional[int] = None
|
||||
for i, d in enumerate(decorators_to_store):
|
||||
impl = get_singledispatch_register_call_info(d, dec.func)
|
||||
if impl is not None:
|
||||
self.singledispatch_impls[impl.singledispatch_func].append(
|
||||
(impl.dispatch_type, dec.func))
|
||||
decorators_to_remove.append(i)
|
||||
if last_non_register is not None:
|
||||
# found a register decorator after a non-register decorator, which we
|
||||
# don't support because we'd have to make a copy of the function before
|
||||
# calling the decorator so that we can call it later, which complicates
|
||||
# the implementation for something that is probably not commonly used
|
||||
self.errors.error(
|
||||
"Calling decorator after registering function not supported",
|
||||
self.current_path,
|
||||
decorators_to_store[last_non_register].line,
|
||||
)
|
||||
else:
|
||||
if refers_to_fullname(d, 'functools.singledispatch'):
|
||||
decorators_to_remove.append(i)
|
||||
# make sure that we still treat the function as a singledispatch function
|
||||
# even if we don't find any registered implementations (which might happen
|
||||
# if all registered implementations are registered dynamically)
|
||||
self.singledispatch_impls.setdefault(dec.func, [])
|
||||
last_non_register = i
|
||||
|
||||
if decorators_to_remove:
|
||||
# calling register on a function that tries to dispatch based on type annotations
|
||||
# raises a TypeError because compiled functions don't have an __annotations__
|
||||
# attribute
|
||||
self.decorators_to_remove[dec.func] = decorators_to_remove
|
||||
|
||||
super().visit_decorator(dec)
|
||||
|
||||
|
||||
class RegisteredImpl(NamedTuple):
|
||||
singledispatch_func: FuncDef
|
||||
dispatch_type: TypeInfo
|
||||
|
||||
|
||||
def get_singledispatch_register_call_info(decorator: Expression, func: FuncDef
|
||||
) -> Optional[RegisteredImpl]:
|
||||
# @fun.register(complex)
|
||||
# def g(arg): ...
|
||||
if (isinstance(decorator, CallExpr) and len(decorator.args) == 1
|
||||
and isinstance(decorator.args[0], RefExpr)):
|
||||
callee = decorator.callee
|
||||
dispatch_type = decorator.args[0].node
|
||||
if not isinstance(dispatch_type, TypeInfo):
|
||||
return None
|
||||
|
||||
if isinstance(callee, MemberExpr):
|
||||
return registered_impl_from_possible_register_call(callee, dispatch_type)
|
||||
# @fun.register
|
||||
# def g(arg: int): ...
|
||||
elif isinstance(decorator, MemberExpr):
|
||||
# we don't know if this is a register call yet, so we can't be sure that the function
|
||||
# actually has arguments
|
||||
if not func.arguments:
|
||||
return None
|
||||
arg_type = get_proper_type(func.arguments[0].variable.type)
|
||||
if not isinstance(arg_type, Instance):
|
||||
return None
|
||||
info = arg_type.type
|
||||
return registered_impl_from_possible_register_call(decorator, info)
|
||||
return None
|
||||
|
||||
|
||||
def registered_impl_from_possible_register_call(expr: MemberExpr, dispatch_type: TypeInfo
|
||||
) -> Optional[RegisteredImpl]:
|
||||
if expr.name == 'register' and isinstance(expr.expr, NameExpr):
|
||||
node = expr.expr.node
|
||||
if isinstance(node, Decorator):
|
||||
return RegisteredImpl(node.func, dispatch_type)
|
||||
return None
|
||||
Binary file not shown.
540
.venv/lib/python3.8/site-packages/mypyc/irbuild/specialize.py
Normal file
540
.venv/lib/python3.8/site-packages/mypyc/irbuild/specialize.py
Normal file
|
|
@ -0,0 +1,540 @@
|
|||
"""Special case IR generation of calls to specific builtin functions.
|
||||
|
||||
Most special cases should be handled using the data driven "primitive
|
||||
ops" system, but certain operations require special handling that has
|
||||
access to the AST/IR directly and can make decisions/optimizations
|
||||
based on it. These special cases can be implemented here.
|
||||
|
||||
For example, we use specializers to statically emit the length of a
|
||||
fixed length tuple and to emit optimized code for any()/all() calls with
|
||||
generator comprehensions as the argument.
|
||||
|
||||
See comment below for more documentation.
|
||||
"""
|
||||
|
||||
from typing import Callable, Optional, Dict, Tuple, List
|
||||
|
||||
from mypy.nodes import (
|
||||
CallExpr, RefExpr, MemberExpr, NameExpr, TupleExpr, GeneratorExpr,
|
||||
ListExpr, DictExpr, StrExpr, IntExpr, ARG_POS, ARG_NAMED, Expression
|
||||
)
|
||||
from mypy.types import AnyType, TypeOfAny
|
||||
|
||||
from mypyc.ir.ops import (
|
||||
Value, Register, BasicBlock, Integer, RaiseStandardError, Unreachable
|
||||
)
|
||||
from mypyc.ir.rtypes import (
|
||||
RType, RTuple, str_rprimitive, list_rprimitive, dict_rprimitive, set_rprimitive,
|
||||
bool_rprimitive, c_int_rprimitive, is_dict_rprimitive
|
||||
)
|
||||
from mypyc.irbuild.format_str_tokenizer import (
|
||||
tokenizer_format_call, join_formatted_strings, convert_format_expr_to_str, FormatOp
|
||||
)
|
||||
from mypyc.primitives.dict_ops import (
|
||||
dict_keys_op, dict_values_op, dict_items_op, dict_setdefault_spec_init_op
|
||||
)
|
||||
from mypyc.primitives.list_ops import new_list_set_item_op
|
||||
from mypyc.primitives.tuple_ops import new_tuple_set_item_op
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
from mypyc.irbuild.for_helpers import (
|
||||
translate_list_comprehension, translate_set_comprehension,
|
||||
comprehension_helper, sequence_from_generator_preallocate_helper
|
||||
)
|
||||
|
||||
# Specializers are attempted before compiling the arguments to the
|
||||
# function. Specializers can return None to indicate that they failed
|
||||
# and the call should be compiled normally. Otherwise they should emit
|
||||
# code for the call and return a Value containing the result.
|
||||
#
|
||||
# Specializers take three arguments: the IRBuilder, the CallExpr being
|
||||
# compiled, and the RefExpr that is the left hand side of the call.
|
||||
Specializer = Callable[['IRBuilder', CallExpr, RefExpr], Optional[Value]]
|
||||
|
||||
# Dictionary containing all configured specializers.
|
||||
#
|
||||
# Specializers can operate on methods as well, and are keyed on the
|
||||
# name and RType in that case.
|
||||
specializers: Dict[Tuple[str, Optional[RType]], List[Specializer]] = {}
|
||||
|
||||
|
||||
def _apply_specialization(builder: 'IRBuilder', expr: CallExpr, callee: RefExpr,
|
||||
name: Optional[str], typ: Optional[RType] = None) -> Optional[Value]:
|
||||
# TODO: Allow special cases to have default args or named args. Currently they don't since
|
||||
# they check that everything in arg_kinds is ARG_POS.
|
||||
|
||||
# If there is a specializer for this function, try calling it.
|
||||
# Return the first successful one.
|
||||
if name and (name, typ) in specializers:
|
||||
for specializer in specializers[name, typ]:
|
||||
val = specializer(builder, expr, callee)
|
||||
if val is not None:
|
||||
return val
|
||||
return None
|
||||
|
||||
|
||||
def apply_function_specialization(builder: 'IRBuilder', expr: CallExpr,
|
||||
callee: RefExpr) -> Optional[Value]:
|
||||
"""Invoke the Specializer callback for a function if one has been registered"""
|
||||
return _apply_specialization(builder, expr, callee, callee.fullname)
|
||||
|
||||
|
||||
def apply_method_specialization(builder: 'IRBuilder', expr: CallExpr, callee: MemberExpr,
|
||||
typ: Optional[RType] = None) -> Optional[Value]:
|
||||
"""Invoke the Specializer callback for a method if one has been registered"""
|
||||
name = callee.fullname if typ is None else callee.name
|
||||
return _apply_specialization(builder, expr, callee, name, typ)
|
||||
|
||||
|
||||
def specialize_function(
|
||||
name: str, typ: Optional[RType] = None) -> Callable[[Specializer], Specializer]:
|
||||
"""Decorator to register a function as being a specializer.
|
||||
|
||||
There may exist multiple specializers for one function. When
|
||||
translating method calls, the earlier appended specializer has
|
||||
higher priority.
|
||||
"""
|
||||
|
||||
def wrapper(f: Specializer) -> Specializer:
|
||||
specializers.setdefault((name, typ), []).append(f)
|
||||
return f
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@specialize_function('builtins.globals')
|
||||
def translate_globals(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
if len(expr.args) == 0:
|
||||
return builder.load_globals_dict()
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.len')
|
||||
def translate_len(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
if (len(expr.args) == 1
|
||||
and expr.arg_kinds == [ARG_POS]):
|
||||
expr_rtype = builder.node_type(expr.args[0])
|
||||
if isinstance(expr_rtype, RTuple):
|
||||
# len() of fixed-length tuple can be trivially determined
|
||||
# statically, though we still need to evaluate it.
|
||||
builder.accept(expr.args[0])
|
||||
return Integer(len(expr_rtype.types))
|
||||
else:
|
||||
obj = builder.accept(expr.args[0])
|
||||
return builder.builtin_len(obj, expr.line)
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.list')
|
||||
def dict_methods_fast_path(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Specialize a common case when list() is called on a dictionary
|
||||
view method call.
|
||||
|
||||
For example:
|
||||
foo = list(bar.keys())
|
||||
"""
|
||||
if not (len(expr.args) == 1 and expr.arg_kinds == [ARG_POS]):
|
||||
return None
|
||||
arg = expr.args[0]
|
||||
if not (isinstance(arg, CallExpr) and not arg.args
|
||||
and isinstance(arg.callee, MemberExpr)):
|
||||
return None
|
||||
base = arg.callee.expr
|
||||
attr = arg.callee.name
|
||||
rtype = builder.node_type(base)
|
||||
if not (is_dict_rprimitive(rtype) and attr in ('keys', 'values', 'items')):
|
||||
return None
|
||||
|
||||
obj = builder.accept(base)
|
||||
# Note that it is not safe to use fast methods on dict subclasses,
|
||||
# so the corresponding helpers in CPy.h fallback to (inlined)
|
||||
# generic logic.
|
||||
if attr == 'keys':
|
||||
return builder.call_c(dict_keys_op, [obj], expr.line)
|
||||
elif attr == 'values':
|
||||
return builder.call_c(dict_values_op, [obj], expr.line)
|
||||
else:
|
||||
return builder.call_c(dict_items_op, [obj], expr.line)
|
||||
|
||||
|
||||
@specialize_function('builtins.list')
|
||||
def translate_list_from_generator_call(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special case for simplest list comprehension.
|
||||
|
||||
For example:
|
||||
list(f(x) for x in some_list/some_tuple/some_str)
|
||||
'translate_list_comprehension()' would take care of other cases
|
||||
if this fails.
|
||||
"""
|
||||
if (len(expr.args) == 1
|
||||
and expr.arg_kinds[0] == ARG_POS
|
||||
and isinstance(expr.args[0], GeneratorExpr)):
|
||||
return sequence_from_generator_preallocate_helper(
|
||||
builder, expr.args[0],
|
||||
empty_op_llbuilder=builder.builder.new_list_op_with_length,
|
||||
set_item_op=new_list_set_item_op)
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.tuple')
|
||||
def translate_tuple_from_generator_call(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special case for simplest tuple creation from a generator.
|
||||
|
||||
For example:
|
||||
tuple(f(x) for x in some_list/some_tuple/some_str)
|
||||
'translate_safe_generator_call()' would take care of other cases
|
||||
if this fails.
|
||||
"""
|
||||
if (len(expr.args) == 1
|
||||
and expr.arg_kinds[0] == ARG_POS
|
||||
and isinstance(expr.args[0], GeneratorExpr)):
|
||||
return sequence_from_generator_preallocate_helper(
|
||||
builder, expr.args[0],
|
||||
empty_op_llbuilder=builder.builder.new_tuple_with_length,
|
||||
set_item_op=new_tuple_set_item_op)
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.set')
|
||||
def translate_set_from_generator_call(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special case for set creation from a generator.
|
||||
|
||||
For example:
|
||||
set(f(...) for ... in iterator/nested_generators...)
|
||||
"""
|
||||
if (len(expr.args) == 1
|
||||
and expr.arg_kinds[0] == ARG_POS
|
||||
and isinstance(expr.args[0], GeneratorExpr)):
|
||||
return translate_set_comprehension(builder, expr.args[0])
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.min')
|
||||
@specialize_function('builtins.max')
|
||||
def faster_min_max(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
if expr.arg_kinds == [ARG_POS, ARG_POS]:
|
||||
x, y = builder.accept(expr.args[0]), builder.accept(expr.args[1])
|
||||
result = Register(builder.node_type(expr))
|
||||
# CPython evaluates arguments reversely when calling min(...) or max(...)
|
||||
if callee.fullname == 'builtins.min':
|
||||
comparison = builder.binary_op(y, x, '<', expr.line)
|
||||
else:
|
||||
comparison = builder.binary_op(y, x, '>', expr.line)
|
||||
|
||||
true_block, false_block, next_block = BasicBlock(), BasicBlock(), BasicBlock()
|
||||
builder.add_bool_branch(comparison, true_block, false_block)
|
||||
|
||||
builder.activate_block(true_block)
|
||||
builder.assign(result, builder.coerce(y, result.type, expr.line), expr.line)
|
||||
builder.goto(next_block)
|
||||
|
||||
builder.activate_block(false_block)
|
||||
builder.assign(result, builder.coerce(x, result.type, expr.line), expr.line)
|
||||
builder.goto(next_block)
|
||||
|
||||
builder.activate_block(next_block)
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.tuple')
|
||||
@specialize_function('builtins.frozenset')
|
||||
@specialize_function('builtins.dict')
|
||||
@specialize_function('builtins.min')
|
||||
@specialize_function('builtins.max')
|
||||
@specialize_function('builtins.sorted')
|
||||
@specialize_function('collections.OrderedDict')
|
||||
@specialize_function('join', str_rprimitive)
|
||||
@specialize_function('extend', list_rprimitive)
|
||||
@specialize_function('update', dict_rprimitive)
|
||||
@specialize_function('update', set_rprimitive)
|
||||
def translate_safe_generator_call(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special cases for things that consume iterators where we know we
|
||||
can safely compile a generator into a list.
|
||||
"""
|
||||
if (len(expr.args) > 0
|
||||
and expr.arg_kinds[0] == ARG_POS
|
||||
and isinstance(expr.args[0], GeneratorExpr)):
|
||||
if isinstance(callee, MemberExpr):
|
||||
return builder.gen_method_call(
|
||||
builder.accept(callee.expr), callee.name,
|
||||
([translate_list_comprehension(builder, expr.args[0])]
|
||||
+ [builder.accept(arg) for arg in expr.args[1:]]),
|
||||
builder.node_type(expr), expr.line, expr.arg_kinds, expr.arg_names)
|
||||
else:
|
||||
return builder.call_refexpr_with_args(
|
||||
expr, callee,
|
||||
([translate_list_comprehension(builder, expr.args[0])]
|
||||
+ [builder.accept(arg) for arg in expr.args[1:]]))
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.any')
|
||||
def translate_any_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
if (len(expr.args) == 1
|
||||
and expr.arg_kinds == [ARG_POS]
|
||||
and isinstance(expr.args[0], GeneratorExpr)):
|
||||
return any_all_helper(builder, expr.args[0], builder.false, lambda x: x, builder.true)
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.all')
|
||||
def translate_all_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
if (len(expr.args) == 1
|
||||
and expr.arg_kinds == [ARG_POS]
|
||||
and isinstance(expr.args[0], GeneratorExpr)):
|
||||
return any_all_helper(
|
||||
builder, expr.args[0],
|
||||
builder.true,
|
||||
lambda x: builder.unary_op(x, 'not', expr.line),
|
||||
builder.false
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def any_all_helper(builder: IRBuilder,
|
||||
gen: GeneratorExpr,
|
||||
initial_value: Callable[[], Value],
|
||||
modify: Callable[[Value], Value],
|
||||
new_value: Callable[[], Value]) -> Value:
|
||||
retval = Register(bool_rprimitive)
|
||||
builder.assign(retval, initial_value(), -1)
|
||||
loop_params = list(zip(gen.indices, gen.sequences, gen.condlists))
|
||||
true_block, false_block, exit_block = BasicBlock(), BasicBlock(), BasicBlock()
|
||||
|
||||
def gen_inner_stmts() -> None:
|
||||
comparison = modify(builder.accept(gen.left_expr))
|
||||
builder.add_bool_branch(comparison, true_block, false_block)
|
||||
builder.activate_block(true_block)
|
||||
builder.assign(retval, new_value(), -1)
|
||||
builder.goto(exit_block)
|
||||
builder.activate_block(false_block)
|
||||
|
||||
comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line)
|
||||
builder.goto_and_activate(exit_block)
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
@specialize_function('builtins.sum')
|
||||
def translate_sum_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
# specialized implementation is used if:
|
||||
# - only one or two arguments given (if not, sum() has been given invalid arguments)
|
||||
# - first argument is a Generator (there is no benefit to optimizing the performance of eg.
|
||||
# sum([1, 2, 3]), so non-Generator Iterables are not handled)
|
||||
if not (len(expr.args) in (1, 2)
|
||||
and expr.arg_kinds[0] == ARG_POS
|
||||
and isinstance(expr.args[0], GeneratorExpr)):
|
||||
return None
|
||||
|
||||
# handle 'start' argument, if given
|
||||
if len(expr.args) == 2:
|
||||
# ensure call to sum() was properly constructed
|
||||
if not expr.arg_kinds[1] in (ARG_POS, ARG_NAMED):
|
||||
return None
|
||||
start_expr = expr.args[1]
|
||||
else:
|
||||
start_expr = IntExpr(0)
|
||||
|
||||
gen_expr = expr.args[0]
|
||||
target_type = builder.node_type(expr)
|
||||
retval = Register(target_type)
|
||||
builder.assign(retval, builder.coerce(builder.accept(start_expr), target_type, -1), -1)
|
||||
|
||||
def gen_inner_stmts() -> None:
|
||||
call_expr = builder.accept(gen_expr.left_expr)
|
||||
builder.assign(retval, builder.binary_op(retval, call_expr, '+', -1), -1)
|
||||
|
||||
loop_params = list(zip(gen_expr.indices, gen_expr.sequences, gen_expr.condlists))
|
||||
comprehension_helper(builder, loop_params, gen_inner_stmts, gen_expr.line)
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
@specialize_function('dataclasses.field')
|
||||
@specialize_function('attr.ib')
|
||||
@specialize_function('attr.attrib')
|
||||
@specialize_function('attr.Factory')
|
||||
def translate_dataclasses_field_call(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special case for 'dataclasses.field', 'attr.attrib', and 'attr.Factory'
|
||||
function calls because the results of such calls are type-checked
|
||||
by mypy using the types of the arguments to their respective
|
||||
functions, resulting in attempted coercions by mypyc that throw a
|
||||
runtime error.
|
||||
"""
|
||||
builder.types[expr] = AnyType(TypeOfAny.from_error)
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('builtins.next')
|
||||
def translate_next_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special case for calling next() on a generator expression, an
|
||||
idiom that shows up some in mypy.
|
||||
|
||||
For example, next(x for x in l if x.id == 12, None) will
|
||||
generate code that searches l for an element where x.id == 12
|
||||
and produce the first such object, or None if no such element
|
||||
exists.
|
||||
"""
|
||||
if not (expr.arg_kinds in ([ARG_POS], [ARG_POS, ARG_POS])
|
||||
and isinstance(expr.args[0], GeneratorExpr)):
|
||||
return None
|
||||
|
||||
gen = expr.args[0]
|
||||
retval = Register(builder.node_type(expr))
|
||||
default_val = builder.accept(expr.args[1]) if len(expr.args) > 1 else None
|
||||
exit_block = BasicBlock()
|
||||
|
||||
def gen_inner_stmts() -> None:
|
||||
# next takes the first element of the generator, so if
|
||||
# something gets produced, we are done.
|
||||
builder.assign(retval, builder.accept(gen.left_expr), gen.left_expr.line)
|
||||
builder.goto(exit_block)
|
||||
|
||||
loop_params = list(zip(gen.indices, gen.sequences, gen.condlists))
|
||||
comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line)
|
||||
|
||||
# Now we need the case for when nothing got hit. If there was
|
||||
# a default value, we produce it, and otherwise we raise
|
||||
# StopIteration.
|
||||
if default_val:
|
||||
builder.assign(retval, default_val, gen.left_expr.line)
|
||||
builder.goto(exit_block)
|
||||
else:
|
||||
builder.add(RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, expr.line))
|
||||
builder.add(Unreachable())
|
||||
|
||||
builder.activate_block(exit_block)
|
||||
return retval
|
||||
|
||||
|
||||
@specialize_function('builtins.isinstance')
|
||||
def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special case for builtins.isinstance.
|
||||
|
||||
Prevent coercions on the thing we are checking the instance of -
|
||||
there is no need to coerce something to a new type before checking
|
||||
what type it is, and the coercion could lead to bugs.
|
||||
"""
|
||||
if (len(expr.args) == 2
|
||||
and expr.arg_kinds == [ARG_POS, ARG_POS]
|
||||
and isinstance(expr.args[1], (RefExpr, TupleExpr))):
|
||||
builder.types[expr.args[0]] = AnyType(TypeOfAny.from_error)
|
||||
|
||||
irs = builder.flatten_classes(expr.args[1])
|
||||
if irs is not None:
|
||||
return builder.builder.isinstance_helper(builder.accept(expr.args[0]), irs, expr.line)
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('setdefault', dict_rprimitive)
|
||||
def translate_dict_setdefault(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special case for 'dict.setdefault' which would only construct
|
||||
default empty collection when needed.
|
||||
|
||||
The dict_setdefault_spec_init_op checks whether the dict contains
|
||||
the key and would construct the empty collection only once.
|
||||
|
||||
For example, this specializer works for the following cases:
|
||||
d.setdefault(key, set()).add(value)
|
||||
d.setdefault(key, []).append(value)
|
||||
d.setdefault(key, {})[inner_key] = inner_val
|
||||
"""
|
||||
if (len(expr.args) == 2
|
||||
and expr.arg_kinds == [ARG_POS, ARG_POS]
|
||||
and isinstance(callee, MemberExpr)):
|
||||
arg = expr.args[1]
|
||||
if isinstance(arg, ListExpr):
|
||||
if len(arg.items):
|
||||
return None
|
||||
data_type = Integer(1, c_int_rprimitive, expr.line)
|
||||
elif isinstance(arg, DictExpr):
|
||||
if len(arg.items):
|
||||
return None
|
||||
data_type = Integer(2, c_int_rprimitive, expr.line)
|
||||
elif (isinstance(arg, CallExpr) and isinstance(arg.callee, NameExpr)
|
||||
and arg.callee.fullname == 'builtins.set'):
|
||||
if len(arg.args):
|
||||
return None
|
||||
data_type = Integer(3, c_int_rprimitive, expr.line)
|
||||
else:
|
||||
return None
|
||||
|
||||
callee_dict = builder.accept(callee.expr)
|
||||
key_val = builder.accept(expr.args[0])
|
||||
return builder.call_c(dict_setdefault_spec_init_op,
|
||||
[callee_dict, key_val, data_type],
|
||||
expr.line)
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('format', str_rprimitive)
|
||||
def translate_str_format(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
if (isinstance(callee, MemberExpr) and isinstance(callee.expr, StrExpr)
|
||||
and expr.arg_kinds.count(ARG_POS) == len(expr.arg_kinds)):
|
||||
format_str = callee.expr.value
|
||||
tokens = tokenizer_format_call(format_str)
|
||||
if tokens is None:
|
||||
return None
|
||||
literals, format_ops = tokens
|
||||
# Convert variables to strings
|
||||
substitutions = convert_format_expr_to_str(builder, format_ops, expr.args, expr.line)
|
||||
if substitutions is None:
|
||||
return None
|
||||
return join_formatted_strings(builder, literals, substitutions, expr.line)
|
||||
return None
|
||||
|
||||
|
||||
@specialize_function('join', str_rprimitive)
|
||||
def translate_fstring(
|
||||
builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
|
||||
"""Special case for f-string, which is translated into str.join()
|
||||
in mypy AST.
|
||||
|
||||
This specializer optimizes simplest f-strings which don't contain
|
||||
any format operation.
|
||||
"""
|
||||
if (isinstance(callee, MemberExpr)
|
||||
and isinstance(callee.expr, StrExpr) and callee.expr.value == ''
|
||||
and expr.arg_kinds == [ARG_POS] and isinstance(expr.args[0], ListExpr)):
|
||||
for item in expr.args[0].items:
|
||||
if isinstance(item, StrExpr):
|
||||
continue
|
||||
elif isinstance(item, CallExpr):
|
||||
if (not isinstance(item.callee, MemberExpr)
|
||||
or item.callee.name != 'format'):
|
||||
return None
|
||||
elif (not isinstance(item.callee.expr, StrExpr)
|
||||
or item.callee.expr.value != '{:{}}'):
|
||||
return None
|
||||
|
||||
if not isinstance(item.args[1], StrExpr) or item.args[1].value != '':
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
format_ops = []
|
||||
exprs: List[Expression] = []
|
||||
|
||||
for item in expr.args[0].items:
|
||||
if isinstance(item, StrExpr) and item.value != '':
|
||||
format_ops.append(FormatOp.STR)
|
||||
exprs.append(item)
|
||||
elif isinstance(item, CallExpr):
|
||||
format_ops.append(FormatOp.STR)
|
||||
exprs.append(item.args[0])
|
||||
|
||||
substitutions = convert_format_expr_to_str(builder, format_ops, exprs, expr.line)
|
||||
if substitutions is None:
|
||||
return None
|
||||
|
||||
return join_formatted_strings(builder, None, substitutions, expr.line)
|
||||
return None
|
||||
Binary file not shown.
682
.venv/lib/python3.8/site-packages/mypyc/irbuild/statement.py
Normal file
682
.venv/lib/python3.8/site-packages/mypyc/irbuild/statement.py
Normal file
|
|
@ -0,0 +1,682 @@
|
|||
"""Transform mypy statement ASTs to mypyc IR (Intermediate Representation).
|
||||
|
||||
The top-level AST transformation logic is implemented in mypyc.irbuild.visitor
|
||||
and mypyc.irbuild.builder.
|
||||
|
||||
A few statements are transformed in mypyc.irbuild.function (yield, for example).
|
||||
"""
|
||||
|
||||
from typing import Optional, List, Tuple, Sequence, Callable
|
||||
import importlib.util
|
||||
|
||||
from mypy.nodes import (
|
||||
Block, ExpressionStmt, ReturnStmt, AssignmentStmt, OperatorAssignmentStmt, IfStmt, WhileStmt,
|
||||
ForStmt, BreakStmt, ContinueStmt, RaiseStmt, TryStmt, WithStmt, AssertStmt, DelStmt,
|
||||
Expression, StrExpr, TempNode, Lvalue, Import, ImportFrom, ImportAll, TupleExpr, ListExpr,
|
||||
StarExpr
|
||||
)
|
||||
|
||||
from mypyc.ir.ops import (
|
||||
Assign, Unreachable, RaiseStandardError, LoadErrorValue, BasicBlock, TupleGet, Value, Register,
|
||||
Branch, NO_TRACEBACK_LINE_NO
|
||||
)
|
||||
from mypyc.ir.rtypes import RInstance, exc_rtuple
|
||||
from mypyc.primitives.generic_ops import py_delattr_op
|
||||
from mypyc.primitives.misc_ops import type_op, import_from_op
|
||||
from mypyc.primitives.exc_ops import (
|
||||
raise_exception_op, reraise_exception_op, error_catch_op, exc_matches_op, restore_exc_info_op,
|
||||
get_exc_value_op, keep_propagating_op, get_exc_info_op
|
||||
)
|
||||
from mypyc.irbuild.targets import (
|
||||
AssignmentTarget, AssignmentTargetRegister, AssignmentTargetIndex, AssignmentTargetAttr,
|
||||
AssignmentTargetTuple
|
||||
)
|
||||
from mypyc.irbuild.nonlocalcontrol import (
|
||||
ExceptNonlocalControl, FinallyNonlocalControl, TryFinallyNonlocalControl
|
||||
)
|
||||
from mypyc.irbuild.for_helpers import for_loop_helper
|
||||
from mypyc.irbuild.builder import IRBuilder
|
||||
|
||||
GenFunc = Callable[[], None]
|
||||
|
||||
|
||||
def transform_block(builder: IRBuilder, block: Block) -> None:
|
||||
if not block.is_unreachable:
|
||||
for stmt in block.body:
|
||||
builder.accept(stmt)
|
||||
# Raise a RuntimeError if we hit a non-empty unreachable block.
|
||||
# Don't complain about empty unreachable blocks, since mypy inserts
|
||||
# those after `if MYPY`.
|
||||
elif block.body:
|
||||
builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR,
|
||||
'Reached allegedly unreachable code!',
|
||||
block.line))
|
||||
builder.add(Unreachable())
|
||||
|
||||
|
||||
def transform_expression_stmt(builder: IRBuilder, stmt: ExpressionStmt) -> None:
|
||||
if isinstance(stmt.expr, StrExpr):
|
||||
# Docstring. Ignore
|
||||
return
|
||||
# ExpressionStmts do not need to be coerced like other Expressions.
|
||||
stmt.expr.accept(builder.visitor)
|
||||
|
||||
|
||||
def transform_return_stmt(builder: IRBuilder, stmt: ReturnStmt) -> None:
|
||||
if stmt.expr:
|
||||
retval = builder.accept(stmt.expr)
|
||||
else:
|
||||
retval = builder.builder.none()
|
||||
retval = builder.coerce(retval, builder.ret_types[-1], stmt.line)
|
||||
builder.nonlocal_control[-1].gen_return(builder, retval, stmt.line)
|
||||
|
||||
|
||||
def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None:
|
||||
lvalues = stmt.lvalues
|
||||
assert len(lvalues) >= 1
|
||||
builder.disallow_class_assignments(lvalues, stmt.line)
|
||||
first_lvalue = lvalues[0]
|
||||
if stmt.type and isinstance(stmt.rvalue, TempNode):
|
||||
# This is actually a variable annotation without initializer. Don't generate
|
||||
# an assignment but we need to call get_assignment_target since it adds a
|
||||
# name binding as a side effect.
|
||||
builder.get_assignment_target(first_lvalue, stmt.line)
|
||||
return
|
||||
|
||||
# Special case multiple assignments like 'x, y = e1, e2'.
|
||||
if (isinstance(first_lvalue, (TupleExpr, ListExpr))
|
||||
and isinstance(stmt.rvalue, (TupleExpr, ListExpr))
|
||||
and len(first_lvalue.items) == len(stmt.rvalue.items)
|
||||
and all(is_simple_lvalue(item) for item in first_lvalue.items)
|
||||
and len(lvalues) == 1):
|
||||
temps = []
|
||||
for right in stmt.rvalue.items:
|
||||
rvalue_reg = builder.accept(right)
|
||||
temp = Register(rvalue_reg.type)
|
||||
builder.assign(temp, rvalue_reg, stmt.line)
|
||||
temps.append(temp)
|
||||
for (left, temp) in zip(first_lvalue.items, temps):
|
||||
assignment_target = builder.get_assignment_target(left)
|
||||
builder.assign(assignment_target, temp, stmt.line)
|
||||
return
|
||||
|
||||
line = stmt.rvalue.line
|
||||
rvalue_reg = builder.accept(stmt.rvalue)
|
||||
if builder.non_function_scope() and stmt.is_final_def:
|
||||
builder.init_final_static(first_lvalue, rvalue_reg)
|
||||
for lvalue in lvalues:
|
||||
target = builder.get_assignment_target(lvalue)
|
||||
builder.assign(target, rvalue_reg, line)
|
||||
|
||||
|
||||
def is_simple_lvalue(expr: Expression) -> bool:
|
||||
return not isinstance(expr, (StarExpr, ListExpr, TupleExpr))
|
||||
|
||||
|
||||
def transform_operator_assignment_stmt(builder: IRBuilder, stmt: OperatorAssignmentStmt) -> None:
|
||||
"""Operator assignment statement such as x += 1"""
|
||||
builder.disallow_class_assignments([stmt.lvalue], stmt.line)
|
||||
target = builder.get_assignment_target(stmt.lvalue)
|
||||
target_value = builder.read(target, stmt.line)
|
||||
rreg = builder.accept(stmt.rvalue)
|
||||
# the Python parser strips the '=' from operator assignment statements, so re-add it
|
||||
op = stmt.op + '='
|
||||
res = builder.binary_op(target_value, rreg, op, stmt.line)
|
||||
# usually operator assignments are done in-place
|
||||
# but when target doesn't support that we need to manually assign
|
||||
builder.assign(target, res, res.line)
|
||||
|
||||
|
||||
def transform_import(builder: IRBuilder, node: Import) -> None:
|
||||
if node.is_mypy_only:
|
||||
return
|
||||
globals = builder.load_globals_dict()
|
||||
for node_id, as_name in node.ids:
|
||||
builder.gen_import(node_id, node.line)
|
||||
|
||||
# Update the globals dict with the appropriate module:
|
||||
# * For 'import foo.bar as baz' we add 'foo.bar' with the name 'baz'
|
||||
# * For 'import foo.bar' we add 'foo' with the name 'foo'
|
||||
# Typically we then ignore these entries and access things directly
|
||||
# via the module static, but we will use the globals version for modules
|
||||
# that mypy couldn't find, since it doesn't analyze module references
|
||||
# from those properly.
|
||||
|
||||
# TODO: Don't add local imports to the global namespace
|
||||
|
||||
# Miscompiling imports inside of functions, like below in import from.
|
||||
if as_name:
|
||||
name = as_name
|
||||
base = node_id
|
||||
else:
|
||||
base = name = node_id.split('.')[0]
|
||||
|
||||
obj = builder.get_module(base, node.line)
|
||||
|
||||
builder.gen_method_call(
|
||||
globals, '__setitem__', [builder.load_str(name), obj],
|
||||
result_type=None, line=node.line)
|
||||
|
||||
|
||||
def transform_import_from(builder: IRBuilder, node: ImportFrom) -> None:
|
||||
if node.is_mypy_only:
|
||||
return
|
||||
|
||||
module_state = builder.graph[builder.module_name]
|
||||
if module_state.ancestors is not None and module_state.ancestors:
|
||||
module_package = module_state.ancestors[0]
|
||||
elif builder.module_path.endswith("__init__.py"):
|
||||
module_package = builder.module_name
|
||||
else:
|
||||
module_package = ''
|
||||
|
||||
id = importlib.util.resolve_name('.' * node.relative + node.id, module_package)
|
||||
|
||||
globals = builder.load_globals_dict()
|
||||
imported_names = [name for name, _ in node.names]
|
||||
module = builder.gen_import_from(id, globals, imported_names, node.line)
|
||||
|
||||
# Copy everything into our module's dict.
|
||||
# Note that we miscompile import from inside of functions here,
|
||||
# since that case *shouldn't* load it into the globals dict.
|
||||
# This probably doesn't matter much and the code runs basically right.
|
||||
for name, maybe_as_name in node.names:
|
||||
as_name = maybe_as_name or name
|
||||
obj = builder.call_c(import_from_op,
|
||||
[module, builder.load_str(id),
|
||||
builder.load_str(name), builder.load_str(as_name)],
|
||||
node.line)
|
||||
builder.gen_method_call(
|
||||
globals, '__setitem__', [builder.load_str(as_name), obj],
|
||||
result_type=None, line=node.line)
|
||||
|
||||
|
||||
def transform_import_all(builder: IRBuilder, node: ImportAll) -> None:
|
||||
if node.is_mypy_only:
|
||||
return
|
||||
builder.gen_import(node.id, node.line)
|
||||
|
||||
|
||||
def transform_if_stmt(builder: IRBuilder, stmt: IfStmt) -> None:
|
||||
if_body, next = BasicBlock(), BasicBlock()
|
||||
else_body = BasicBlock() if stmt.else_body else next
|
||||
|
||||
# If statements are normalized
|
||||
assert len(stmt.expr) == 1
|
||||
|
||||
builder.process_conditional(stmt.expr[0], if_body, else_body)
|
||||
builder.activate_block(if_body)
|
||||
builder.accept(stmt.body[0])
|
||||
builder.goto(next)
|
||||
if stmt.else_body:
|
||||
builder.activate_block(else_body)
|
||||
builder.accept(stmt.else_body)
|
||||
builder.goto(next)
|
||||
builder.activate_block(next)
|
||||
|
||||
|
||||
def transform_while_stmt(builder: IRBuilder, s: WhileStmt) -> None:
|
||||
body, next, top, else_block = BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock()
|
||||
normal_loop_exit = else_block if s.else_body is not None else next
|
||||
|
||||
builder.push_loop_stack(top, next)
|
||||
|
||||
# Split block so that we get a handle to the top of the loop.
|
||||
builder.goto_and_activate(top)
|
||||
builder.process_conditional(s.expr, body, normal_loop_exit)
|
||||
|
||||
builder.activate_block(body)
|
||||
builder.accept(s.body)
|
||||
# Add branch to the top at the end of the body.
|
||||
builder.goto(top)
|
||||
|
||||
builder.pop_loop_stack()
|
||||
|
||||
if s.else_body is not None:
|
||||
builder.activate_block(else_block)
|
||||
builder.accept(s.else_body)
|
||||
builder.goto(next)
|
||||
|
||||
builder.activate_block(next)
|
||||
|
||||
|
||||
def transform_for_stmt(builder: IRBuilder, s: ForStmt) -> None:
|
||||
if s.is_async:
|
||||
builder.error('async for is unimplemented', s.line)
|
||||
|
||||
def body() -> None:
|
||||
builder.accept(s.body)
|
||||
|
||||
def else_block() -> None:
|
||||
assert s.else_body is not None
|
||||
builder.accept(s.else_body)
|
||||
|
||||
for_loop_helper(builder, s.index, s.expr, body,
|
||||
else_block if s.else_body else None, s.line)
|
||||
|
||||
|
||||
def transform_break_stmt(builder: IRBuilder, node: BreakStmt) -> None:
|
||||
builder.nonlocal_control[-1].gen_break(builder, node.line)
|
||||
|
||||
|
||||
def transform_continue_stmt(builder: IRBuilder, node: ContinueStmt) -> None:
|
||||
builder.nonlocal_control[-1].gen_continue(builder, node.line)
|
||||
|
||||
|
||||
def transform_raise_stmt(builder: IRBuilder, s: RaiseStmt) -> None:
|
||||
if s.expr is None:
|
||||
builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
|
||||
builder.add(Unreachable())
|
||||
return
|
||||
|
||||
exc = builder.accept(s.expr)
|
||||
builder.call_c(raise_exception_op, [exc], s.line)
|
||||
builder.add(Unreachable())
|
||||
|
||||
|
||||
def transform_try_except(builder: IRBuilder,
|
||||
body: GenFunc,
|
||||
handlers: Sequence[
|
||||
Tuple[Optional[Expression], Optional[Expression], GenFunc]],
|
||||
else_body: Optional[GenFunc],
|
||||
line: int) -> None:
|
||||
"""Generalized try/except/else handling that takes functions to gen the bodies.
|
||||
|
||||
The point of this is to also be able to support with."""
|
||||
assert handlers, "try needs except"
|
||||
|
||||
except_entry, exit_block, cleanup_block = BasicBlock(), BasicBlock(), BasicBlock()
|
||||
double_except_block = BasicBlock()
|
||||
# If there is an else block, jump there after the try, otherwise just leave
|
||||
else_block = BasicBlock() if else_body else exit_block
|
||||
|
||||
# Compile the try block with an error handler
|
||||
builder.builder.push_error_handler(except_entry)
|
||||
builder.goto_and_activate(BasicBlock())
|
||||
body()
|
||||
builder.goto(else_block)
|
||||
builder.builder.pop_error_handler()
|
||||
|
||||
# The error handler catches the error and then checks it
|
||||
# against the except clauses. We compile the error handler
|
||||
# itself with an error handler so that it can properly restore
|
||||
# the *old* exc_info if an exception occurs.
|
||||
# The exception chaining will be done automatically when the
|
||||
# exception is raised, based on the exception in exc_info.
|
||||
builder.builder.push_error_handler(double_except_block)
|
||||
builder.activate_block(except_entry)
|
||||
old_exc = builder.maybe_spill(builder.call_c(error_catch_op, [], line))
|
||||
# Compile the except blocks with the nonlocal control flow overridden to clear exc_info
|
||||
builder.nonlocal_control.append(
|
||||
ExceptNonlocalControl(builder.nonlocal_control[-1], old_exc))
|
||||
|
||||
# Process the bodies
|
||||
for type, var, handler_body in handlers:
|
||||
next_block = None
|
||||
if type:
|
||||
next_block, body_block = BasicBlock(), BasicBlock()
|
||||
matches = builder.call_c(
|
||||
exc_matches_op, [builder.accept(type)], type.line
|
||||
)
|
||||
builder.add(Branch(matches, body_block, next_block, Branch.BOOL))
|
||||
builder.activate_block(body_block)
|
||||
if var:
|
||||
target = builder.get_assignment_target(var)
|
||||
builder.assign(
|
||||
target,
|
||||
builder.call_c(get_exc_value_op, [], var.line),
|
||||
var.line
|
||||
)
|
||||
handler_body()
|
||||
builder.goto(cleanup_block)
|
||||
if next_block:
|
||||
builder.activate_block(next_block)
|
||||
|
||||
# Reraise the exception if needed
|
||||
if next_block:
|
||||
builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
|
||||
builder.add(Unreachable())
|
||||
|
||||
builder.nonlocal_control.pop()
|
||||
builder.builder.pop_error_handler()
|
||||
|
||||
# Cleanup for if we leave except through normal control flow:
|
||||
# restore the saved exc_info information and continue propagating
|
||||
# the exception if it exists.
|
||||
builder.activate_block(cleanup_block)
|
||||
builder.call_c(restore_exc_info_op, [builder.read(old_exc)], line)
|
||||
builder.goto(exit_block)
|
||||
|
||||
# Cleanup for if we leave except through a raised exception:
|
||||
# restore the saved exc_info information and continue propagating
|
||||
# the exception.
|
||||
builder.activate_block(double_except_block)
|
||||
builder.call_c(restore_exc_info_op, [builder.read(old_exc)], line)
|
||||
builder.call_c(keep_propagating_op, [], NO_TRACEBACK_LINE_NO)
|
||||
builder.add(Unreachable())
|
||||
|
||||
# If present, compile the else body in the obvious way
|
||||
if else_body:
|
||||
builder.activate_block(else_block)
|
||||
else_body()
|
||||
builder.goto(exit_block)
|
||||
|
||||
builder.activate_block(exit_block)
|
||||
|
||||
|
||||
def transform_try_except_stmt(builder: IRBuilder, t: TryStmt) -> None:
|
||||
def body() -> None:
|
||||
builder.accept(t.body)
|
||||
|
||||
# Work around scoping woes
|
||||
def make_handler(body: Block) -> GenFunc:
|
||||
return lambda: builder.accept(body)
|
||||
|
||||
handlers = [(type, var, make_handler(body))
|
||||
for type, var, body in zip(t.types, t.vars, t.handlers)]
|
||||
else_body = (lambda: builder.accept(t.else_body)) if t.else_body else None
|
||||
transform_try_except(builder, body, handlers, else_body, t.line)
|
||||
|
||||
|
||||
def try_finally_try(builder: IRBuilder,
|
||||
err_handler: BasicBlock,
|
||||
return_entry: BasicBlock,
|
||||
main_entry: BasicBlock,
|
||||
try_body: GenFunc) -> Optional[Register]:
|
||||
# Compile the try block with an error handler
|
||||
control = TryFinallyNonlocalControl(return_entry)
|
||||
builder.builder.push_error_handler(err_handler)
|
||||
|
||||
builder.nonlocal_control.append(control)
|
||||
builder.goto_and_activate(BasicBlock())
|
||||
try_body()
|
||||
builder.goto(main_entry)
|
||||
builder.nonlocal_control.pop()
|
||||
builder.builder.pop_error_handler()
|
||||
|
||||
return control.ret_reg
|
||||
|
||||
|
||||
def try_finally_entry_blocks(builder: IRBuilder,
|
||||
err_handler: BasicBlock,
|
||||
return_entry: BasicBlock,
|
||||
main_entry: BasicBlock,
|
||||
finally_block: BasicBlock,
|
||||
ret_reg: Optional[Register]) -> Value:
|
||||
old_exc = Register(exc_rtuple)
|
||||
|
||||
# Entry block for non-exceptional flow
|
||||
builder.activate_block(main_entry)
|
||||
if ret_reg:
|
||||
builder.add(
|
||||
Assign(
|
||||
ret_reg,
|
||||
builder.add(LoadErrorValue(builder.ret_types[-1]))
|
||||
)
|
||||
)
|
||||
builder.goto(return_entry)
|
||||
|
||||
builder.activate_block(return_entry)
|
||||
builder.add(Assign(old_exc, builder.add(LoadErrorValue(exc_rtuple))))
|
||||
builder.goto(finally_block)
|
||||
|
||||
# Entry block for errors
|
||||
builder.activate_block(err_handler)
|
||||
if ret_reg:
|
||||
builder.add(
|
||||
Assign(
|
||||
ret_reg,
|
||||
builder.add(LoadErrorValue(builder.ret_types[-1]))
|
||||
)
|
||||
)
|
||||
builder.add(Assign(old_exc, builder.call_c(error_catch_op, [], -1)))
|
||||
builder.goto(finally_block)
|
||||
|
||||
return old_exc
|
||||
|
||||
|
||||
def try_finally_body(
|
||||
builder: IRBuilder,
|
||||
finally_block: BasicBlock,
|
||||
finally_body: GenFunc,
|
||||
ret_reg: Optional[Value],
|
||||
old_exc: Value) -> Tuple[BasicBlock, FinallyNonlocalControl]:
|
||||
cleanup_block = BasicBlock()
|
||||
# Compile the finally block with the nonlocal control flow overridden to restore exc_info
|
||||
builder.builder.push_error_handler(cleanup_block)
|
||||
finally_control = FinallyNonlocalControl(
|
||||
builder.nonlocal_control[-1], ret_reg, old_exc)
|
||||
builder.nonlocal_control.append(finally_control)
|
||||
builder.activate_block(finally_block)
|
||||
finally_body()
|
||||
builder.nonlocal_control.pop()
|
||||
|
||||
return cleanup_block, finally_control
|
||||
|
||||
|
||||
def try_finally_resolve_control(builder: IRBuilder,
|
||||
cleanup_block: BasicBlock,
|
||||
finally_control: FinallyNonlocalControl,
|
||||
old_exc: Value,
|
||||
ret_reg: Optional[Value]) -> BasicBlock:
|
||||
"""Resolve the control flow out of a finally block.
|
||||
|
||||
This means returning if there was a return, propagating
|
||||
exceptions, break/continue (soon), or just continuing on.
|
||||
"""
|
||||
reraise, rest = BasicBlock(), BasicBlock()
|
||||
builder.add(Branch(old_exc, rest, reraise, Branch.IS_ERROR))
|
||||
|
||||
# Reraise the exception if there was one
|
||||
builder.activate_block(reraise)
|
||||
builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
|
||||
builder.add(Unreachable())
|
||||
builder.builder.pop_error_handler()
|
||||
|
||||
# If there was a return, keep returning
|
||||
if ret_reg:
|
||||
builder.activate_block(rest)
|
||||
return_block, rest = BasicBlock(), BasicBlock()
|
||||
builder.add(Branch(ret_reg, rest, return_block, Branch.IS_ERROR))
|
||||
|
||||
builder.activate_block(return_block)
|
||||
builder.nonlocal_control[-1].gen_return(builder, ret_reg, -1)
|
||||
|
||||
# TODO: handle break/continue
|
||||
builder.activate_block(rest)
|
||||
out_block = BasicBlock()
|
||||
builder.goto(out_block)
|
||||
|
||||
# If there was an exception, restore again
|
||||
builder.activate_block(cleanup_block)
|
||||
finally_control.gen_cleanup(builder, -1)
|
||||
builder.call_c(keep_propagating_op, [], NO_TRACEBACK_LINE_NO)
|
||||
builder.add(Unreachable())
|
||||
|
||||
return out_block
|
||||
|
||||
|
||||
def transform_try_finally_stmt(builder: IRBuilder,
|
||||
try_body: GenFunc,
|
||||
finally_body: GenFunc) -> None:
|
||||
"""Generalized try/finally handling that takes functions to gen the bodies.
|
||||
|
||||
The point of this is to also be able to support with."""
|
||||
# Finally is a big pain, because there are so many ways that
|
||||
# exits can occur. We emit 10+ basic blocks for every finally!
|
||||
|
||||
err_handler, main_entry, return_entry, finally_block = (
|
||||
BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock())
|
||||
|
||||
# Compile the body of the try
|
||||
ret_reg = try_finally_try(
|
||||
builder, err_handler, return_entry, main_entry, try_body)
|
||||
|
||||
# Set up the entry blocks for the finally statement
|
||||
old_exc = try_finally_entry_blocks(
|
||||
builder, err_handler, return_entry, main_entry, finally_block, ret_reg)
|
||||
|
||||
# Compile the body of the finally
|
||||
cleanup_block, finally_control = try_finally_body(
|
||||
builder, finally_block, finally_body, ret_reg, old_exc)
|
||||
|
||||
# Resolve the control flow out of the finally block
|
||||
out_block = try_finally_resolve_control(
|
||||
builder, cleanup_block, finally_control, old_exc, ret_reg)
|
||||
|
||||
builder.activate_block(out_block)
|
||||
|
||||
|
||||
def transform_try_stmt(builder: IRBuilder, t: TryStmt) -> None:
|
||||
# Our compilation strategy for try/except/else/finally is to
|
||||
# treat try/except/else and try/finally as separate language
|
||||
# constructs that we compile separately. When we have a
|
||||
# try/except/else/finally, we treat the try/except/else as the
|
||||
# body of a try/finally block.
|
||||
if t.finally_body:
|
||||
def transform_try_body() -> None:
|
||||
if t.handlers:
|
||||
transform_try_except_stmt(builder, t)
|
||||
else:
|
||||
builder.accept(t.body)
|
||||
body = t.finally_body
|
||||
|
||||
transform_try_finally_stmt(builder, transform_try_body, lambda: builder.accept(body))
|
||||
else:
|
||||
transform_try_except_stmt(builder, t)
|
||||
|
||||
|
||||
def get_sys_exc_info(builder: IRBuilder) -> List[Value]:
|
||||
exc_info = builder.call_c(get_exc_info_op, [], -1)
|
||||
return [builder.add(TupleGet(exc_info, i, -1)) for i in range(3)]
|
||||
|
||||
|
||||
def transform_with(builder: IRBuilder,
|
||||
expr: Expression,
|
||||
target: Optional[Lvalue],
|
||||
body: GenFunc,
|
||||
line: int) -> None:
|
||||
# This is basically a straight transcription of the Python code in PEP 343.
|
||||
# I don't actually understand why a bunch of it is the way it is.
|
||||
# We could probably optimize the case where the manager is compiled by us,
|
||||
# but that is not our common case at all, so.
|
||||
mgr_v = builder.accept(expr)
|
||||
typ = builder.call_c(type_op, [mgr_v], line)
|
||||
exit_ = builder.maybe_spill(builder.py_get_attr(typ, '__exit__', line))
|
||||
value = builder.py_call(
|
||||
builder.py_get_attr(typ, '__enter__', line), [mgr_v], line
|
||||
)
|
||||
mgr = builder.maybe_spill(mgr_v)
|
||||
exc = builder.maybe_spill_assignable(builder.true())
|
||||
|
||||
def try_body() -> None:
|
||||
if target:
|
||||
builder.assign(builder.get_assignment_target(target), value, line)
|
||||
body()
|
||||
|
||||
def except_body() -> None:
|
||||
builder.assign(exc, builder.false(), line)
|
||||
out_block, reraise_block = BasicBlock(), BasicBlock()
|
||||
builder.add_bool_branch(
|
||||
builder.py_call(builder.read(exit_),
|
||||
[builder.read(mgr)] + get_sys_exc_info(builder), line),
|
||||
out_block,
|
||||
reraise_block
|
||||
)
|
||||
builder.activate_block(reraise_block)
|
||||
builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
|
||||
builder.add(Unreachable())
|
||||
builder.activate_block(out_block)
|
||||
|
||||
def finally_body() -> None:
|
||||
out_block, exit_block = BasicBlock(), BasicBlock()
|
||||
builder.add(
|
||||
Branch(builder.read(exc), exit_block, out_block, Branch.BOOL)
|
||||
)
|
||||
builder.activate_block(exit_block)
|
||||
none = builder.none_object()
|
||||
builder.py_call(
|
||||
builder.read(exit_), [builder.read(mgr), none, none, none], line
|
||||
)
|
||||
builder.goto_and_activate(out_block)
|
||||
|
||||
transform_try_finally_stmt(
|
||||
builder,
|
||||
lambda: transform_try_except(builder,
|
||||
try_body,
|
||||
[(None, None, except_body)],
|
||||
None,
|
||||
line),
|
||||
finally_body
|
||||
)
|
||||
|
||||
|
||||
def transform_with_stmt(builder: IRBuilder, o: WithStmt) -> None:
|
||||
if o.is_async:
|
||||
builder.error('async with is unimplemented', o.line)
|
||||
|
||||
# Generate separate logic for each expr in it, left to right
|
||||
def generate(i: int) -> None:
|
||||
if i >= len(o.expr):
|
||||
builder.accept(o.body)
|
||||
else:
|
||||
transform_with(builder, o.expr[i], o.target[i], lambda: generate(i + 1), o.line)
|
||||
|
||||
generate(0)
|
||||
|
||||
|
||||
def transform_assert_stmt(builder: IRBuilder, a: AssertStmt) -> None:
|
||||
if builder.options.strip_asserts:
|
||||
return
|
||||
cond = builder.accept(a.expr)
|
||||
ok_block, error_block = BasicBlock(), BasicBlock()
|
||||
builder.add_bool_branch(cond, ok_block, error_block)
|
||||
builder.activate_block(error_block)
|
||||
if a.msg is None:
|
||||
# Special case (for simpler generated code)
|
||||
builder.add(RaiseStandardError(RaiseStandardError.ASSERTION_ERROR, None, a.line))
|
||||
elif isinstance(a.msg, StrExpr):
|
||||
# Another special case
|
||||
builder.add(RaiseStandardError(RaiseStandardError.ASSERTION_ERROR, a.msg.value,
|
||||
a.line))
|
||||
else:
|
||||
# The general case -- explicitly construct an exception instance
|
||||
message = builder.accept(a.msg)
|
||||
exc_type = builder.load_module_attr_by_fullname('builtins.AssertionError', a.line)
|
||||
exc = builder.py_call(exc_type, [message], a.line)
|
||||
builder.call_c(raise_exception_op, [exc], a.line)
|
||||
builder.add(Unreachable())
|
||||
builder.activate_block(ok_block)
|
||||
|
||||
|
||||
def transform_del_stmt(builder: IRBuilder, o: DelStmt) -> None:
|
||||
transform_del_item(builder, builder.get_assignment_target(o.expr), o.line)
|
||||
|
||||
|
||||
def transform_del_item(builder: IRBuilder, target: AssignmentTarget, line: int) -> None:
|
||||
if isinstance(target, AssignmentTargetIndex):
|
||||
builder.gen_method_call(
|
||||
target.base,
|
||||
'__delitem__',
|
||||
[target.index],
|
||||
result_type=None,
|
||||
line=line
|
||||
)
|
||||
elif isinstance(target, AssignmentTargetAttr):
|
||||
if isinstance(target.obj_type, RInstance):
|
||||
cl = target.obj_type.class_ir
|
||||
if not cl.is_deletable(target.attr):
|
||||
builder.error('"{}" cannot be deleted'.format(target.attr), line)
|
||||
builder.note(
|
||||
'Using "__deletable__ = ' +
|
||||
'[\'<attr>\']" in the class body enables "del obj.<attr>"', line)
|
||||
key = builder.load_str(target.attr)
|
||||
builder.call_c(py_delattr_op, [target.obj, key], line)
|
||||
elif isinstance(target, AssignmentTargetRegister):
|
||||
# Delete a local by assigning an error value to it, which will
|
||||
# prompt the insertion of uninit checks.
|
||||
builder.add(Assign(target.register,
|
||||
builder.add(LoadErrorValue(target.type, undefines=True))))
|
||||
elif isinstance(target, AssignmentTargetTuple):
|
||||
for subtarget in target.items:
|
||||
transform_del_item(builder, subtarget, line)
|
||||
Binary file not shown.
58
.venv/lib/python3.8/site-packages/mypyc/irbuild/targets.py
Normal file
58
.venv/lib/python3.8/site-packages/mypyc/irbuild/targets.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from mypyc.ir.ops import Value, Register
|
||||
from mypyc.ir.rtypes import RType, RInstance, object_rprimitive
|
||||
|
||||
|
||||
class AssignmentTarget:
|
||||
"""Abstract base class for assignment targets during IR building."""
|
||||
|
||||
type: RType = object_rprimitive
|
||||
|
||||
|
||||
class AssignmentTargetRegister(AssignmentTarget):
|
||||
"""Register as an assignment target.
|
||||
|
||||
This is used for local variables and some temporaries.
|
||||
"""
|
||||
|
||||
def __init__(self, register: Register) -> None:
|
||||
self.register = register
|
||||
self.type = register.type
|
||||
|
||||
|
||||
class AssignmentTargetIndex(AssignmentTarget):
|
||||
"""base[index] as assignment target"""
|
||||
|
||||
def __init__(self, base: Value, index: Value) -> None:
|
||||
self.base = base
|
||||
self.index = index
|
||||
# TODO: object_rprimitive won't be right for user-defined classes. Store the
|
||||
# lvalue type in mypy and use a better type to avoid unneeded boxing.
|
||||
self.type = object_rprimitive
|
||||
|
||||
|
||||
class AssignmentTargetAttr(AssignmentTarget):
|
||||
"""obj.attr as assignment target"""
|
||||
|
||||
def __init__(self, obj: Value, attr: str) -> None:
|
||||
self.obj = obj
|
||||
self.attr = attr
|
||||
if isinstance(obj.type, RInstance) and obj.type.class_ir.has_attr(attr):
|
||||
# Native attribute reference
|
||||
self.obj_type: RType = obj.type
|
||||
self.type = obj.type.attr_type(attr)
|
||||
else:
|
||||
# Python attribute reference
|
||||
self.obj_type = object_rprimitive
|
||||
self.type = object_rprimitive
|
||||
|
||||
|
||||
class AssignmentTargetTuple(AssignmentTarget):
|
||||
"""x, ..., y as assignment target"""
|
||||
|
||||
def __init__(self,
|
||||
items: List[AssignmentTarget],
|
||||
star_idx: Optional[int] = None) -> None:
|
||||
self.items = items
|
||||
self.star_idx = star_idx
|
||||
Binary file not shown.
156
.venv/lib/python3.8/site-packages/mypyc/irbuild/util.py
Normal file
156
.venv/lib/python3.8/site-packages/mypyc/irbuild/util.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
"""Various utilities that don't depend on other modules in mypyc.irbuild."""
|
||||
|
||||
from typing import Dict, Any, Union, Optional
|
||||
|
||||
from mypy.nodes import (
|
||||
ClassDef, FuncDef, Decorator, OverloadedFuncDef, StrExpr, CallExpr, RefExpr, Expression,
|
||||
IntExpr, FloatExpr, Var, NameExpr, TupleExpr, UnaryExpr, BytesExpr,
|
||||
ArgKind, ARG_NAMED, ARG_NAMED_OPT, ARG_POS, ARG_OPT, GDEF,
|
||||
)
|
||||
|
||||
|
||||
DATACLASS_DECORATORS = {
|
||||
'dataclasses.dataclass',
|
||||
'attr.s',
|
||||
'attr.attrs',
|
||||
}
|
||||
|
||||
|
||||
def is_trait_decorator(d: Expression) -> bool:
|
||||
return isinstance(d, RefExpr) and d.fullname == 'mypy_extensions.trait'
|
||||
|
||||
|
||||
def is_trait(cdef: ClassDef) -> bool:
|
||||
return any(is_trait_decorator(d) for d in cdef.decorators) or cdef.info.is_protocol
|
||||
|
||||
|
||||
def dataclass_decorator_type(d: Expression) -> Optional[str]:
|
||||
if isinstance(d, RefExpr) and d.fullname in DATACLASS_DECORATORS:
|
||||
return d.fullname.split('.')[0]
|
||||
elif (isinstance(d, CallExpr)
|
||||
and isinstance(d.callee, RefExpr)
|
||||
and d.callee.fullname in DATACLASS_DECORATORS):
|
||||
name = d.callee.fullname.split('.')[0]
|
||||
if name == 'attr' and 'auto_attribs' in d.arg_names:
|
||||
# Note: the mypy attrs plugin checks that the value of auto_attribs is
|
||||
# not computed at runtime, so we don't need to perform that check here
|
||||
auto = d.args[d.arg_names.index('auto_attribs')]
|
||||
if isinstance(auto, NameExpr) and auto.name == 'True':
|
||||
return 'attr-auto'
|
||||
return name
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def is_dataclass_decorator(d: Expression) -> bool:
|
||||
return dataclass_decorator_type(d) is not None
|
||||
|
||||
|
||||
def is_dataclass(cdef: ClassDef) -> bool:
|
||||
return any(is_dataclass_decorator(d) for d in cdef.decorators)
|
||||
|
||||
|
||||
def dataclass_type(cdef: ClassDef) -> Optional[str]:
|
||||
for d in cdef.decorators:
|
||||
typ = dataclass_decorator_type(d)
|
||||
if typ is not None:
|
||||
return typ
|
||||
return None
|
||||
|
||||
|
||||
def get_mypyc_attr_literal(e: Expression) -> Any:
|
||||
"""Convert an expression from a mypyc_attr decorator to a value.
|
||||
|
||||
Supports a pretty limited range."""
|
||||
if isinstance(e, (StrExpr, IntExpr, FloatExpr)):
|
||||
return e.value
|
||||
elif isinstance(e, RefExpr) and e.fullname == 'builtins.True':
|
||||
return True
|
||||
elif isinstance(e, RefExpr) and e.fullname == 'builtins.False':
|
||||
return False
|
||||
elif isinstance(e, RefExpr) and e.fullname == 'builtins.None':
|
||||
return None
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def get_mypyc_attr_call(d: Expression) -> Optional[CallExpr]:
|
||||
"""Check if an expression is a call to mypyc_attr and return it if so."""
|
||||
if (
|
||||
isinstance(d, CallExpr)
|
||||
and isinstance(d.callee, RefExpr)
|
||||
and d.callee.fullname == 'mypy_extensions.mypyc_attr'
|
||||
):
|
||||
return d
|
||||
return None
|
||||
|
||||
|
||||
def get_mypyc_attrs(stmt: Union[ClassDef, Decorator]) -> Dict[str, Any]:
|
||||
"""Collect all the mypyc_attr attributes on a class definition or a function."""
|
||||
attrs: Dict[str, Any] = {}
|
||||
for dec in stmt.decorators:
|
||||
d = get_mypyc_attr_call(dec)
|
||||
if d:
|
||||
for name, arg in zip(d.arg_names, d.args):
|
||||
if name is None:
|
||||
if isinstance(arg, StrExpr):
|
||||
attrs[arg.value] = True
|
||||
else:
|
||||
attrs[name] = get_mypyc_attr_literal(arg)
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
def is_extension_class(cdef: ClassDef) -> bool:
|
||||
if any(
|
||||
not is_trait_decorator(d)
|
||||
and not is_dataclass_decorator(d)
|
||||
and not get_mypyc_attr_call(d)
|
||||
for d in cdef.decorators
|
||||
):
|
||||
return False
|
||||
if cdef.info.typeddict_type:
|
||||
return False
|
||||
if cdef.info.is_named_tuple:
|
||||
return False
|
||||
if (cdef.info.metaclass_type and cdef.info.metaclass_type.type.fullname not in (
|
||||
'abc.ABCMeta', 'typing.TypingMeta', 'typing.GenericMeta')):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_func_def(op: Union[FuncDef, Decorator, OverloadedFuncDef]) -> FuncDef:
|
||||
if isinstance(op, OverloadedFuncDef):
|
||||
assert op.impl
|
||||
op = op.impl
|
||||
if isinstance(op, Decorator):
|
||||
op = op.func
|
||||
return op
|
||||
|
||||
|
||||
def concrete_arg_kind(kind: ArgKind) -> ArgKind:
|
||||
"""Find the concrete version of an arg kind that is being passed."""
|
||||
if kind == ARG_OPT:
|
||||
return ARG_POS
|
||||
elif kind == ARG_NAMED_OPT:
|
||||
return ARG_NAMED
|
||||
else:
|
||||
return kind
|
||||
|
||||
|
||||
def is_constant(e: Expression) -> bool:
|
||||
"""Check whether we allow an expression to appear as a default value.
|
||||
|
||||
We don't currently properly support storing the evaluated
|
||||
values for default arguments and default attribute values, so
|
||||
we restrict what expressions we allow. We allow literals of
|
||||
primitives types, None, and references to Final global
|
||||
variables.
|
||||
"""
|
||||
return (isinstance(e, (StrExpr, BytesExpr, IntExpr, FloatExpr))
|
||||
or (isinstance(e, UnaryExpr) and e.op == '-'
|
||||
and isinstance(e.expr, (IntExpr, FloatExpr)))
|
||||
or (isinstance(e, TupleExpr)
|
||||
and all(is_constant(e) for e in e.items))
|
||||
or (isinstance(e, RefExpr) and e.kind == GDEF
|
||||
and (e.fullname in ('builtins.True', 'builtins.False', 'builtins.None')
|
||||
or (isinstance(e.node, Var) and e.node.is_final))))
|
||||
Binary file not shown.
343
.venv/lib/python3.8/site-packages/mypyc/irbuild/visitor.py
Normal file
343
.venv/lib/python3.8/site-packages/mypyc/irbuild/visitor.py
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
"""Dispatcher used when transforming a mypy AST to the IR form.
|
||||
|
||||
mypyc.irbuild.builder and mypyc.irbuild.main are closely related.
|
||||
"""
|
||||
|
||||
from typing_extensions import NoReturn
|
||||
|
||||
from mypy.nodes import (
|
||||
MypyFile, FuncDef, ReturnStmt, AssignmentStmt, OpExpr,
|
||||
IntExpr, NameExpr, Var, IfStmt, UnaryExpr, ComparisonExpr, WhileStmt, CallExpr,
|
||||
IndexExpr, Block, ListExpr, ExpressionStmt, MemberExpr, ForStmt,
|
||||
BreakStmt, ContinueStmt, ConditionalExpr, OperatorAssignmentStmt, TupleExpr, ClassDef,
|
||||
Import, ImportFrom, ImportAll, DictExpr, StrExpr, CastExpr, TempNode,
|
||||
PassStmt, PromoteExpr, AssignmentExpr, AwaitExpr, BackquoteExpr, AssertStmt, BytesExpr,
|
||||
ComplexExpr, Decorator, DelStmt, DictionaryComprehension, EllipsisExpr, EnumCallExpr, ExecStmt,
|
||||
FloatExpr, GeneratorExpr, GlobalDecl, LambdaExpr, ListComprehension, SetComprehension,
|
||||
NamedTupleExpr, NewTypeExpr, NonlocalDecl, OverloadedFuncDef, PrintStmt, RaiseStmt,
|
||||
RevealExpr, SetExpr, SliceExpr, StarExpr, SuperExpr, TryStmt, TypeAliasExpr, TypeApplication,
|
||||
TypeVarExpr, TypedDictExpr, UnicodeExpr, WithStmt, YieldFromExpr, YieldExpr, ParamSpecExpr,
|
||||
MatchStmt
|
||||
)
|
||||
|
||||
from mypyc.ir.ops import Value
|
||||
from mypyc.irbuild.builder import IRVisitor, IRBuilder, UnsupportedException
|
||||
from mypyc.irbuild.classdef import transform_class_def
|
||||
from mypyc.irbuild.function import (
|
||||
transform_func_def,
|
||||
transform_overloaded_func_def,
|
||||
transform_decorator,
|
||||
transform_lambda_expr,
|
||||
transform_yield_expr,
|
||||
transform_yield_from_expr,
|
||||
transform_await_expr,
|
||||
)
|
||||
from mypyc.irbuild.statement import (
|
||||
transform_block,
|
||||
transform_expression_stmt,
|
||||
transform_return_stmt,
|
||||
transform_assignment_stmt,
|
||||
transform_operator_assignment_stmt,
|
||||
transform_import,
|
||||
transform_import_from,
|
||||
transform_import_all,
|
||||
transform_if_stmt,
|
||||
transform_while_stmt,
|
||||
transform_for_stmt,
|
||||
transform_break_stmt,
|
||||
transform_continue_stmt,
|
||||
transform_raise_stmt,
|
||||
transform_try_stmt,
|
||||
transform_with_stmt,
|
||||
transform_assert_stmt,
|
||||
transform_del_stmt,
|
||||
)
|
||||
from mypyc.irbuild.expression import (
|
||||
transform_name_expr,
|
||||
transform_member_expr,
|
||||
transform_super_expr,
|
||||
transform_call_expr,
|
||||
transform_unary_expr,
|
||||
transform_op_expr,
|
||||
transform_index_expr,
|
||||
transform_conditional_expr,
|
||||
transform_int_expr,
|
||||
transform_float_expr,
|
||||
transform_complex_expr,
|
||||
transform_comparison_expr,
|
||||
transform_str_expr,
|
||||
transform_bytes_expr,
|
||||
transform_ellipsis,
|
||||
transform_list_expr,
|
||||
transform_tuple_expr,
|
||||
transform_dict_expr,
|
||||
transform_set_expr,
|
||||
transform_list_comprehension,
|
||||
transform_set_comprehension,
|
||||
transform_dictionary_comprehension,
|
||||
transform_slice_expr,
|
||||
transform_generator_expr,
|
||||
transform_assignment_expr,
|
||||
)
|
||||
|
||||
|
||||
class IRBuilderVisitor(IRVisitor):
|
||||
"""Mypy node visitor that dispatches to node transform implementations.
|
||||
|
||||
This class should have no non-trivial logic.
|
||||
|
||||
This visitor is separated from the rest of code to improve modularity and
|
||||
to avoid import cycles.
|
||||
|
||||
This is based on the visitor pattern
|
||||
(https://en.wikipedia.org/wiki/Visitor_pattern).
|
||||
"""
|
||||
|
||||
# This gets passed to all the implementations and contains all the
|
||||
# state and many helpers. The attribute is initialized outside
|
||||
# this class since this class and IRBuilder form a reference loop.
|
||||
builder: IRBuilder
|
||||
|
||||
def visit_mypy_file(self, mypyfile: MypyFile) -> None:
|
||||
assert False, "use transform_mypy_file instead"
|
||||
|
||||
def visit_class_def(self, cdef: ClassDef) -> None:
|
||||
transform_class_def(self.builder, cdef)
|
||||
|
||||
def visit_import(self, node: Import) -> None:
|
||||
transform_import(self.builder, node)
|
||||
|
||||
def visit_import_from(self, node: ImportFrom) -> None:
|
||||
transform_import_from(self.builder, node)
|
||||
|
||||
def visit_import_all(self, node: ImportAll) -> None:
|
||||
transform_import_all(self.builder, node)
|
||||
|
||||
def visit_func_def(self, fdef: FuncDef) -> None:
|
||||
transform_func_def(self.builder, fdef)
|
||||
|
||||
def visit_overloaded_func_def(self, o: OverloadedFuncDef) -> None:
|
||||
transform_overloaded_func_def(self.builder, o)
|
||||
|
||||
def visit_decorator(self, dec: Decorator) -> None:
|
||||
transform_decorator(self.builder, dec)
|
||||
|
||||
def visit_block(self, block: Block) -> None:
|
||||
transform_block(self.builder, block)
|
||||
|
||||
# Statements
|
||||
|
||||
def visit_expression_stmt(self, stmt: ExpressionStmt) -> None:
|
||||
transform_expression_stmt(self.builder, stmt)
|
||||
|
||||
def visit_return_stmt(self, stmt: ReturnStmt) -> None:
|
||||
transform_return_stmt(self.builder, stmt)
|
||||
|
||||
def visit_assignment_stmt(self, stmt: AssignmentStmt) -> None:
|
||||
transform_assignment_stmt(self.builder, stmt)
|
||||
|
||||
def visit_operator_assignment_stmt(self, stmt: OperatorAssignmentStmt) -> None:
|
||||
transform_operator_assignment_stmt(self.builder, stmt)
|
||||
|
||||
def visit_if_stmt(self, stmt: IfStmt) -> None:
|
||||
transform_if_stmt(self.builder, stmt)
|
||||
|
||||
def visit_while_stmt(self, stmt: WhileStmt) -> None:
|
||||
transform_while_stmt(self.builder, stmt)
|
||||
|
||||
def visit_for_stmt(self, stmt: ForStmt) -> None:
|
||||
transform_for_stmt(self.builder, stmt)
|
||||
|
||||
def visit_break_stmt(self, stmt: BreakStmt) -> None:
|
||||
transform_break_stmt(self.builder, stmt)
|
||||
|
||||
def visit_continue_stmt(self, stmt: ContinueStmt) -> None:
|
||||
transform_continue_stmt(self.builder, stmt)
|
||||
|
||||
def visit_raise_stmt(self, stmt: RaiseStmt) -> None:
|
||||
transform_raise_stmt(self.builder, stmt)
|
||||
|
||||
def visit_try_stmt(self, stmt: TryStmt) -> None:
|
||||
transform_try_stmt(self.builder, stmt)
|
||||
|
||||
def visit_with_stmt(self, stmt: WithStmt) -> None:
|
||||
transform_with_stmt(self.builder, stmt)
|
||||
|
||||
def visit_pass_stmt(self, stmt: PassStmt) -> None:
|
||||
pass
|
||||
|
||||
def visit_assert_stmt(self, stmt: AssertStmt) -> None:
|
||||
transform_assert_stmt(self.builder, stmt)
|
||||
|
||||
def visit_del_stmt(self, stmt: DelStmt) -> None:
|
||||
transform_del_stmt(self.builder, stmt)
|
||||
|
||||
def visit_global_decl(self, stmt: GlobalDecl) -> None:
|
||||
# Pure declaration -- no runtime effect
|
||||
pass
|
||||
|
||||
def visit_nonlocal_decl(self, stmt: NonlocalDecl) -> None:
|
||||
# Pure declaration -- no runtime effect
|
||||
pass
|
||||
|
||||
def visit_match_stmt(self, stmt: MatchStmt) -> None:
|
||||
self.bail("Match statements are not yet supported", stmt.line)
|
||||
|
||||
# Expressions
|
||||
|
||||
def visit_name_expr(self, expr: NameExpr) -> Value:
|
||||
return transform_name_expr(self.builder, expr)
|
||||
|
||||
def visit_member_expr(self, expr: MemberExpr) -> Value:
|
||||
return transform_member_expr(self.builder, expr)
|
||||
|
||||
def visit_super_expr(self, expr: SuperExpr) -> Value:
|
||||
return transform_super_expr(self.builder, expr)
|
||||
|
||||
def visit_call_expr(self, expr: CallExpr) -> Value:
|
||||
return transform_call_expr(self.builder, expr)
|
||||
|
||||
def visit_unary_expr(self, expr: UnaryExpr) -> Value:
|
||||
return transform_unary_expr(self.builder, expr)
|
||||
|
||||
def visit_op_expr(self, expr: OpExpr) -> Value:
|
||||
return transform_op_expr(self.builder, expr)
|
||||
|
||||
def visit_index_expr(self, expr: IndexExpr) -> Value:
|
||||
return transform_index_expr(self.builder, expr)
|
||||
|
||||
def visit_conditional_expr(self, expr: ConditionalExpr) -> Value:
|
||||
return transform_conditional_expr(self.builder, expr)
|
||||
|
||||
def visit_comparison_expr(self, expr: ComparisonExpr) -> Value:
|
||||
return transform_comparison_expr(self.builder, expr)
|
||||
|
||||
def visit_int_expr(self, expr: IntExpr) -> Value:
|
||||
return transform_int_expr(self.builder, expr)
|
||||
|
||||
def visit_float_expr(self, expr: FloatExpr) -> Value:
|
||||
return transform_float_expr(self.builder, expr)
|
||||
|
||||
def visit_complex_expr(self, expr: ComplexExpr) -> Value:
|
||||
return transform_complex_expr(self.builder, expr)
|
||||
|
||||
def visit_str_expr(self, expr: StrExpr) -> Value:
|
||||
return transform_str_expr(self.builder, expr)
|
||||
|
||||
def visit_bytes_expr(self, expr: BytesExpr) -> Value:
|
||||
return transform_bytes_expr(self.builder, expr)
|
||||
|
||||
def visit_ellipsis(self, expr: EllipsisExpr) -> Value:
|
||||
return transform_ellipsis(self.builder, expr)
|
||||
|
||||
def visit_list_expr(self, expr: ListExpr) -> Value:
|
||||
return transform_list_expr(self.builder, expr)
|
||||
|
||||
def visit_tuple_expr(self, expr: TupleExpr) -> Value:
|
||||
return transform_tuple_expr(self.builder, expr)
|
||||
|
||||
def visit_dict_expr(self, expr: DictExpr) -> Value:
|
||||
return transform_dict_expr(self.builder, expr)
|
||||
|
||||
def visit_set_expr(self, expr: SetExpr) -> Value:
|
||||
return transform_set_expr(self.builder, expr)
|
||||
|
||||
def visit_list_comprehension(self, expr: ListComprehension) -> Value:
|
||||
return transform_list_comprehension(self.builder, expr)
|
||||
|
||||
def visit_set_comprehension(self, expr: SetComprehension) -> Value:
|
||||
return transform_set_comprehension(self.builder, expr)
|
||||
|
||||
def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> Value:
|
||||
return transform_dictionary_comprehension(self.builder, expr)
|
||||
|
||||
def visit_slice_expr(self, expr: SliceExpr) -> Value:
|
||||
return transform_slice_expr(self.builder, expr)
|
||||
|
||||
def visit_generator_expr(self, expr: GeneratorExpr) -> Value:
|
||||
return transform_generator_expr(self.builder, expr)
|
||||
|
||||
def visit_lambda_expr(self, expr: LambdaExpr) -> Value:
|
||||
return transform_lambda_expr(self.builder, expr)
|
||||
|
||||
def visit_yield_expr(self, expr: YieldExpr) -> Value:
|
||||
return transform_yield_expr(self.builder, expr)
|
||||
|
||||
def visit_yield_from_expr(self, o: YieldFromExpr) -> Value:
|
||||
return transform_yield_from_expr(self.builder, o)
|
||||
|
||||
def visit_await_expr(self, o: AwaitExpr) -> Value:
|
||||
return transform_await_expr(self.builder, o)
|
||||
|
||||
def visit_assignment_expr(self, o: AssignmentExpr) -> Value:
|
||||
return transform_assignment_expr(self.builder, o)
|
||||
|
||||
# Unimplemented constructs that shouldn't come up because they are py2 only
|
||||
|
||||
def visit_backquote_expr(self, o: BackquoteExpr) -> Value:
|
||||
self.bail("Python 2 features are unsupported", o.line)
|
||||
|
||||
def visit_exec_stmt(self, o: ExecStmt) -> None:
|
||||
self.bail("Python 2 features are unsupported", o.line)
|
||||
|
||||
def visit_print_stmt(self, o: PrintStmt) -> None:
|
||||
self.bail("Python 2 features are unsupported", o.line)
|
||||
|
||||
def visit_unicode_expr(self, o: UnicodeExpr) -> Value:
|
||||
self.bail("Python 2 features are unsupported", o.line)
|
||||
|
||||
# Constructs that shouldn't ever show up
|
||||
|
||||
def visit_enum_call_expr(self, o: EnumCallExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit__promote_expr(self, o: PromoteExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_namedtuple_expr(self, o: NamedTupleExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_newtype_expr(self, o: NewTypeExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_temp_node(self, o: TempNode) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_type_alias_expr(self, o: TypeAliasExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_type_application(self, o: TypeApplication) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_type_var_expr(self, o: TypeVarExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_paramspec_expr(self, o: ParamSpecExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_typeddict_expr(self, o: TypedDictExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_reveal_expr(self, o: RevealExpr) -> Value:
|
||||
assert False, "can't compile analysis-only expressions"
|
||||
|
||||
def visit_var(self, o: Var) -> None:
|
||||
assert False, "can't compile Var; should have been handled already?"
|
||||
|
||||
def visit_cast_expr(self, o: CastExpr) -> Value:
|
||||
assert False, "CastExpr should have been handled in CallExpr"
|
||||
|
||||
def visit_star_expr(self, o: StarExpr) -> Value:
|
||||
assert False, "should have been handled in Tuple/List/Set/DictExpr or CallExpr"
|
||||
|
||||
# Helpers
|
||||
|
||||
def bail(self, msg: str, line: int) -> NoReturn:
|
||||
"""Reports an error and aborts compilation up until the last accept() call
|
||||
|
||||
(accept() catches the UnsupportedException and keeps on
|
||||
processing. This allows errors to be non-blocking without always
|
||||
needing to write handling for them.
|
||||
"""
|
||||
self.builder.error(msg, line)
|
||||
raise UnsupportedException()
|
||||
Binary file not shown.
75
.venv/lib/python3.8/site-packages/mypyc/irbuild/vtable.py
Normal file
75
.venv/lib/python3.8/site-packages/mypyc/irbuild/vtable.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
"""Compute vtables of native (extension) classes."""
|
||||
|
||||
import itertools
|
||||
|
||||
from mypyc.ir.class_ir import ClassIR, VTableEntries, VTableMethod
|
||||
from mypyc.sametype import is_same_method_signature
|
||||
|
||||
|
||||
def compute_vtable(cls: ClassIR) -> None:
|
||||
"""Compute the vtable structure for a class."""
|
||||
if cls.vtable is not None:
|
||||
return
|
||||
|
||||
if not cls.is_generated:
|
||||
cls.has_dict = any(x.inherits_python for x in cls.mro)
|
||||
|
||||
for t in cls.mro[1:]:
|
||||
# Make sure all ancestors are processed first
|
||||
compute_vtable(t)
|
||||
# Merge attributes from traits into the class
|
||||
if not t.is_trait:
|
||||
continue
|
||||
for name, typ in t.attributes.items():
|
||||
if not cls.is_trait and not any(name in b.attributes for b in cls.base_mro):
|
||||
cls.attributes[name] = typ
|
||||
|
||||
cls.vtable = {}
|
||||
if cls.base:
|
||||
assert cls.base.vtable is not None
|
||||
cls.vtable.update(cls.base.vtable)
|
||||
cls.vtable_entries = specialize_parent_vtable(cls, cls.base)
|
||||
|
||||
# Include the vtable from the parent classes, but handle method overrides.
|
||||
entries = cls.vtable_entries
|
||||
|
||||
all_traits = [t for t in cls.mro if t.is_trait]
|
||||
|
||||
for t in [cls] + cls.traits:
|
||||
for fn in itertools.chain(t.methods.values()):
|
||||
# TODO: don't generate a new entry when we overload without changing the type
|
||||
if fn == cls.get_method(fn.name):
|
||||
cls.vtable[fn.name] = len(entries)
|
||||
# If the class contains a glue method referring to itself, that is a
|
||||
# shadow glue method to support interpreted subclasses.
|
||||
shadow = cls.glue_methods.get((cls, fn.name))
|
||||
entries.append(VTableMethod(t, fn.name, fn, shadow))
|
||||
|
||||
# Compute vtables for all of the traits that the class implements
|
||||
if not cls.is_trait:
|
||||
for trait in all_traits:
|
||||
compute_vtable(trait)
|
||||
cls.trait_vtables[trait] = specialize_parent_vtable(cls, trait)
|
||||
|
||||
|
||||
def specialize_parent_vtable(cls: ClassIR, parent: ClassIR) -> VTableEntries:
|
||||
"""Generate the part of a vtable corresponding to a parent class or trait"""
|
||||
updated = []
|
||||
for entry in parent.vtable_entries:
|
||||
# Find the original method corresponding to this vtable entry.
|
||||
# (This may not be the method in the entry, if it was overridden.)
|
||||
orig_parent_method = entry.cls.get_method(entry.name)
|
||||
assert orig_parent_method
|
||||
method_cls = cls.get_method_and_class(entry.name)
|
||||
if method_cls:
|
||||
child_method, defining_cls = method_cls
|
||||
# TODO: emit a wrapper for __init__ that raises or something
|
||||
if (is_same_method_signature(orig_parent_method.sig, child_method.sig)
|
||||
or orig_parent_method.name == '__init__'):
|
||||
entry = VTableMethod(entry.cls, entry.name, child_method, entry.shadow_method)
|
||||
else:
|
||||
entry = VTableMethod(entry.cls, entry.name,
|
||||
defining_cls.glue_methods[(entry.cls, entry.name)],
|
||||
entry.shadow_method)
|
||||
updated.append(entry)
|
||||
return updated
|
||||
Loading…
Add table
Add a link
Reference in a new issue