def dataclass_non_ext_info(self, 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( self.primitive_op(new_dict_op, [], cdef.line), self.add(TupleSet([], cdef.line)), self.primitive_op(new_dict_op, [], cdef.line), self.primitive_op(type_object_op, [], cdef.line), ) else: return None
def visit_class_def(self, cdef: ClassDef) -> None: ir = self.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)): self.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: self.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 = self.allocate_class(cdef) # type: Optional[Value] non_ext = None # type: Optional[NonExtClassInfo] dataclass_non_ext = self.dataclass_non_ext_info(cdef) else: non_ext_bases = self.populate_non_ext_bases(cdef) non_ext_metaclass = self.find_non_ext_metaclass(cdef, non_ext_bases) non_ext_dict = self.setup_non_ext_dict(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.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 self.error("Property setters not supported in non-extension classes", stmt.line) for item in stmt.items: with self.builder.catch_errors(stmt.line): BuildFuncIR(self.builder).visit_method(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 self.builder.catch_errors(stmt.line): BuildFuncIR(self.builder).visit_method(cdef, non_ext, get_func_def(stmt)) elif isinstance(stmt, PassStmt): continue elif isinstance(stmt, AssignmentStmt): if len(stmt.lvalues) != 1: self.error("Multiple assignment in class bodies not supported", stmt.line) continue lvalue = stmt.lvalues[0] if not isinstance(lvalue, NameExpr): self.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: self.add_non_ext_class_attr(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 = self.builder.load_native_type_object(cdef.fullname) value = self.accept(stmt.rvalue) self.primitive_op( py_setattr_op, [typ, self.load_static_unicode(lvalue.name), value], stmt.line) if self.builder.non_function_scope() and stmt.is_final_def: self.builder.init_final_static(lvalue, value, cdef.name) elif isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr): # Docstring. Ignore pass else: self.error("Unsupported statement in class body", stmt.line) if not non_ext: # That is, an extension class self.generate_attr_defaults(cdef) self.create_ne_from_eq(cdef) if dataclass_non_ext: assert type_obj self.dataclass_finalize(cdef, dataclass_non_ext, type_obj) else: # Dynamically create the class via the type constructor non_ext_class = self.load_non_ext_class(ir, non_ext, cdef.line) non_ext_class = self.load_decorated_class(cdef, non_ext_class) # Save the decorated class self.add(InitStatic(non_ext_class, cdef.name, self.module_name, NAMESPACE_TYPE)) # Add the non-extension class to the dict self.primitive_op(dict_set_item_op, [ self.builder.load_globals_dict(), self.load_static_unicode(cdef.name), non_ext_class ], cdef.line) # Cache any cachable class attributes self.cache_class_attrs(attrs_to_cache, cdef) # Set this attribute back to None until the next non-extension class is visited. self.non_ext_info = None