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 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__') return builder.primitive_op(new_tuple_op, [builder.load_static_unicode(attr) for attr in attrs], line)
def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: SuperExpr) -> Value: if callee.info is None or callee.call.args: return translate_call(builder, expr, callee) ir = builder.mapper.type_to_ir[callee.info] # Search for the method in the mro, skipping ourselves. for base in ir.mro[1:]: if callee.name in base.method_decls: break else: 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: vself = next(iter(builder.environment.indexes)) # grab first argument if decl.kind == FUNC_CLASSMETHOD: vself = builder.primitive_op(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.environment.symtable.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 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 cache_class_attrs(builder: IRBuilder, attrs_to_cache: List[Lvalue], cdef: ClassDef) -> None: """Add class attributes to be cached to the global cache""" typ = builder.load_native_type_object(cdef.fullname) for lval 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)
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: declared_metaclass = builder.primitive_op(type_object_op, [], cdef.line) return builder.primitive_op(py_calc_meta_op, [declared_metaclass, bases], cdef.line)
def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: base = builder.accept(expr.base) if isinstance(base.type, RTuple) and isinstance(expr.index, IntExpr): return builder.add(TupleGet(base, expr.index.value, expr.line)) index_reg = builder.accept(expr.index) return builder.gen_method_call(base, '__getitem__', [index_reg], builder.node_type(expr), 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 translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: # Special case builtins.isinstance if (len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS] and isinstance(expr.args[1], (RefExpr, TupleExpr))): 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
def transform_for_stmt(builder: IRBuilder, s: ForStmt) -> None: def body() -> None: builder.accept(s.body) def else_block() -> None: assert s.else_body is not None builder.accept(s.else_body) builder.for_loop_helper(s.index, s.expr, body, else_block if s.else_body else None, s.line)
def translate_len(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: # Special case builtins.len 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 builder.add(LoadInt(len(expr_rtype.types))) return None
def transform_dictionary_comprehension(builder: IRBuilder, o: DictionaryComprehension) -> Value: d = builder.primitive_op(new_dict_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.primitive_op(dict_set_item_op, [d, k, v], o.line) builder.comprehension_helper(loop_params, gen_inner_stmts, o.line) return d
def transform_set_comprehension(builder: IRBuilder, o: SetComprehension) -> Value: gen = o.generator set_ops = builder.primitive_op(new_set_op, [], o.line) loop_params = list(zip(gen.indices, gen.sequences, gen.condlists)) def gen_inner_stmts() -> None: e = builder.accept(gen.left_expr) builder.primitive_op(set_add_op, [set_ops, e], o.line) builder.comprehension_helper(loop_params, gen_inner_stmts, o.line) return set_ops
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.primitive_op(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.primitive_op(true_op, [], -1)) def try_body() -> None: if target: builder.assign(builder.get_assignment_target(target), value, line) body() def except_body() -> None: builder.assign(exc, builder.primitive_op(false_op, [], -1), 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.primitive_op(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_EXPR) ) 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_raise_stmt(builder: IRBuilder, s: RaiseStmt) -> None: if s.expr is None: builder.primitive_op(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) return exc = builder.accept(s.expr) builder.primitive_op(raise_exception_op, [exc], s.line) builder.add(Unreachable())
def translate_call(builder: IRBuilder, expr: CallExpr, callee: Expression) -> Value: # The common case of calls is refexprs if isinstance(callee, RefExpr): return 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 transform_basic_comparison(builder: IRBuilder, op: str, left: Value, right: Value, line: int) -> Value: 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 load_non_ext_class(builder: IRBuilder, ir: ClassIR, non_ext: NonExtClassInfo, line: int) -> Value: cls_name = builder.load_static_unicode(ir.name) finish_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 dataclass_non_ext_info(builder: IRBuilder, cdef: ClassDef) -> Optional[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. """ if is_dataclass(cdef): return NonExtClassInfo( builder.primitive_op(new_dict_op, [], cdef.line), builder.add(TupleSet([], cdef.line)), builder.primitive_op(new_dict_op, [], cdef.line), builder.primitive_op(type_object_op, [], cdef.line), ) else: return None
def translate_refexpr_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value: """Translate a non-method call.""" # 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. if callee.fullname and (callee.fullname, None) in specializers: val = specializers[callee.fullname, None](builder, expr, callee) if val is not None: return val # 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_dataclasses_field_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: # Special case for 'dataclasses.field' and 'attr.Factory' function calls # because the results of such calls are typechecked 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
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_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 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) return builder.builder.get_attr(obj, expr.name, builder.node_type(expr), expr.line)
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], true_op, lambda x: builder.unary_op(x, 'not', expr.line), false_op) return None
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 add_non_ext_class_attr(builder: IRBuilder, non_ext: NonExtClassInfo, lvalue: NameExpr, stmt: AssignmentStmt, cdef: ClassDef, attr_to_cache: List[Lvalue]) -> None: """ Add a class attribute to __annotations__ of a non-extension class. If the attribute is assigned to a value, it is also added to __dict__. """ # We populate __annotations__ because dataclasses uses it to determine # which attributes to compute on. # TODO: Maybe generate more precise types for annotations key = builder.load_static_unicode(lvalue.name) typ = builder.primitive_op(type_object_op, [], stmt.line) builder.primitive_op(dict_set_item_op, [non_ext.anns, key, typ], stmt.line) # 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 "_order_" and "__order__", since Enum will remove it and lvalue.name not in ('_order_', '__order__') ): attr_to_cache.append(lvalue)
def finish_non_ext_dict(builder: IRBuilder, non_ext: NonExtClassInfo, line: int) -> None: # Add __annotations__ to the class dict. builder.primitive_op(dict_set_item_op, [non_ext.dict, builder.load_static_unicode('__annotations__'), non_ext.anns], -1) # 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_static_unicode(filler_doc_str), line) builder.add_to_non_ext_dict( non_ext, '__module__', builder.load_static_unicode(builder.module_name), line)
def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: assert expr.node, "RefExpr not resolved" fullname = expr.node.fullname if fullname in name_ref_ops: # Use special access op for this particular name. desc = name_ref_ops[fullname] assert desc.result_type is not None return builder.add(PrimitiveOp([], desc, expr.line)) 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 and FuncDef node types. return builder.read(builder.get_assignment_target(expr), expr.line) return builder.load_global(expr)
def populate_non_ext_bases(builder: IRBuilder, cdef: ClassDef) -> Value: """ Populate the base-class tuple passed to the metaclass constructor for non-extension classes. """ ir = builder.mapper.type_to_ir[cdef.info] bases = [] for cls in cdef.info.mro[1:]: if cls.fullname == 'builtins.object': 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) base = builder.load_global_str(cls.name, cdef.line) bases.append(base) return builder.primitive_op(new_tuple_op, bases, cdef.line)