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.primitive_op(py_hasattr_op, [metaclass, builder.load_static_unicode('__prepare__')], cdef.line) non_ext_dict = builder.alloc_temp(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_static_unicode(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.primitive_op(new_dict_op, [], cdef.line), cdef.line) builder.goto(exit_block) builder.activate_block(exit_block) return non_ext_dict
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 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 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 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.primitive_op(new_tuple_op, bases, cdef.line) else: tp_bases = builder.add(LoadErrorValue(object_rprimitive, is_borrowed=True)) modname = builder.load_static_unicode(builder.module_name) template = builder.add(LoadStatic(object_rprimitive, cdef.name + "_template", builder.module_name, NAMESPACE_TYPE)) # Create the class tp = builder.primitive_op(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.primitive_op(py_setattr_op, [ tp, builder.load_static_unicode('__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.primitive_op(dict_set_item_op, [ builder.load_globals_dict(), builder.load_static_unicode(cdef.name), tp, ], cdef.line) return tp
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 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): key = builder.load_static_unicode(target.attr) builder.add(PrimitiveOp([target.obj, key], py_delattr_op, 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)
def transform_str_expr(builder: IRBuilder, expr: StrExpr) -> Value: return builder.load_static_unicode(expr.value)
def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None: 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: # If the class is not decorated, generate an extension class for it. type_obj = allocate_class(builder, cdef) # type: Optional[Value] non_ext = None # type: Optional[NonExtClassInfo] dataclass_non_ext = dataclass_non_ext_info(builder, cdef) else: non_ext_bases = populate_non_ext_bases(builder, cdef) non_ext_metaclass = find_non_ext_metaclass(builder, cdef, non_ext_bases) non_ext_dict = setup_non_ext_dict(builder, 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 = builder.primitive_op(new_dict_op, [], cdef.line) non_ext = NonExtClassInfo(non_ext_dict, non_ext_bases, non_ext_anns, non_ext_metaclass) dataclass_non_ext = None type_obj = None attrs_to_cache = [] # type: List[Lvalue] for stmt in cdef.defs.body: if isinstance(stmt, OverloadedFuncDef) and stmt.is_property: if not ir.is_ext_class: # 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): transform_method(builder, cdef, non_ext, 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): transform_method(builder, cdef, non_ext, 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. var_non_ext = non_ext or dataclass_non_ext if var_non_ext: add_non_ext_class_attr(builder, var_non_ext, lvalue, stmt, cdef, attrs_to_cache) if non_ext: continue # Variable declaration with no body if isinstance(stmt.rvalue, TempNode): continue # Only treat marked class variables as class variables. if not (is_class_var(lvalue) or stmt.is_final_def): continue typ = builder.load_native_type_object(cdef.fullname) value = builder.accept(stmt.rvalue) builder.primitive_op( py_setattr_op, [typ, builder.load_static_unicode(lvalue.name), value], stmt.line) if builder.non_function_scope() and stmt.is_final_def: builder.init_final_static(lvalue, value, cdef.name) elif isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr): # Docstring. Ignore pass else: builder.error("Unsupported statement in class body", stmt.line) if not non_ext: # That is, an extension class generate_attr_defaults(builder, cdef) create_ne_from_eq(builder, cdef) if dataclass_non_ext: assert type_obj dataclass_finalize(builder, cdef, dataclass_non_ext, type_obj) else: # Dynamically create the class via the type constructor non_ext_class = load_non_ext_class(builder, ir, non_ext, cdef.line) non_ext_class = load_decorated_class(builder, cdef, non_ext_class) # Save the decorated class builder.add(InitStatic(non_ext_class, cdef.name, builder.module_name, NAMESPACE_TYPE)) # Add the non-extension class to the dict builder.primitive_op(dict_set_item_op, [ builder.load_globals_dict(), builder.load_static_unicode(cdef.name), non_ext_class ], cdef.line) # Cache any cachable class attributes cache_class_attrs(builder, attrs_to_cache, cdef)