165 lines
7.2 KiB
Python
165 lines
7.2 KiB
Python
"""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 = f'{builder.fn_info.namespaced_name()}_obj'
|
|
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
|