| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- """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 __future__ import annotations
- from mypyc.common import ENV_ATTR_NAME, SELF_NAME
- from mypyc.ir.class_ir import ClassIR
- from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature, RuntimeArg
- from mypyc.ir.ops import BasicBlock, Call, Register, Return, SetAttr, Value
- from mypyc.ir.rtypes import RInstance, object_rprimitive
- 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.
- nargs = len(sig.args) - sig.num_bitmap_args
- sig = FuncSignature(
- (RuntimeArg(SELF_NAME, object_rprimitive),) + sig.args[:nargs], 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
|