def _handle_initvar(self, node, cls, name, typ, orig): """Unpack or delete an initvar in the class annotations.""" initvar = match_initvar(typ) if not initvar: return None # The InitVar annotation is not retained as a class member, but any default # value is retained. if orig is None: # If an initvar does not have a default, it will not be a class member # variable, so delete it from the annotated locals. Otherwise, leave the # annotation as InitVar[...]. del self.vm.annotated_locals[cls.name][name] else: classgen.add_member(node, cls, name, initvar) return initvar
def decorate(self, node, cls): """Processes class members.""" # Collect classvars to convert them to attrs. @dataclass collects vars with # an explicit type annotation, in order of annotation, so that e.g. # class A: # x: int # y: str = 'hello' # x = 10 # would have init(x:int = 10, y:str = 'hello') own_attrs = [] cls_locals = self.get_class_locals(node, cls) for name, local in cls_locals.items(): typ, orig = local.get_type(node, name), local.orig kind = "" assert typ if match_classvar(typ): continue initvar_typ = self._handle_initvar(node, cls, name, typ, orig) if initvar_typ: typ = initvar_typ init = True kind = classgen.AttributeKinds.INITVAR else: if not orig: classgen.add_member(node, cls, name, typ) if is_field(orig): field = orig.data[0] orig = field.default init = field.init else: init = True if orig and orig.data == [self.vm.convert.none]: # vm._apply_annotation mostly takes care of checking that the default # matches the declared type. However, it allows None defaults, and # dataclasses do not. self.vm.check_annotation_type_mismatch(node, name, typ, orig, local.stack, allow_none=False) attr = classgen.Attribute(name=name, typ=typ, init=init, kw_only=False, default=orig, kind=kind) own_attrs.append(attr) cls.record_attr_ordering(own_attrs) attrs = cls.compute_attr_metadata(own_attrs, "dataclasses.dataclass") # Add an __init__ method if one doesn't exist already (dataclasses do not # overwrite an explicit __init__ method). if "__init__" not in cls.members and self.args[cls]["init"]: init_method = self.make_init(node, cls, attrs) cls.members["__init__"] = init_method if isinstance(cls, abstract.InterpreterClass): cls.decorators.append("dataclasses.dataclass") # Fix up type parameters in methods added by the decorator. cls.update_method_type_params()
def decorate(self, node, cls): """Processes the attrib members of a class.""" # Collect classvars to convert them to attrs. new_auto_attribs, ordering = self._handle_auto_attribs( self.args[cls]["auto_attribs"], self.ctx.vm.local_ops.get(cls.name, ()), cls.name) if new_auto_attribs is not _NO_CHANGE: self.args[cls]["auto_attribs"] = new_auto_attribs ordered_locals = classgen.get_class_locals( cls.name, allow_methods=False, ordering=ordering, ctx=self.ctx) own_attrs = [] for name, local in ordered_locals.items(): typ, orig = local.get_type(node, name), local.orig if is_attrib(orig): attrib = orig.data[0] attr = Attribute( name=name, typ=None, init=attrib.init, init_type=attrib.init_type, kw_only=attrib.kw_only, default=attrib.default) if typ: if attrib.type_source == TypeSource.TYPE: # We cannot have both a type annotation and a type argument. msg = "attr.ib cannot have both a 'type' arg and a type annotation." self.ctx.errorlog.invalid_annotation( self.ctx.vm.frames, typ, details=msg) attr.typ = self.ctx.convert.unsolvable elif attrib.type_source == TypeSource.CONVERTER: msg = "attr.ib type has been assigned by the converter." self.ctx.check_annotation_type_mismatch( node, name, typ, attrib.typ.instantiate(node), local.stack, allow_none=True, details=msg) attr.typ = typ else: # cls.members[name] has already been set via a type annotation # Use the annotation type as the field type in all circumstances; if # it conflicts with the `type` or `converter` args we will raise an # error above, and if it is compatible but not identical we treat it # as the type most expressive of the code's intent. attr.typ = typ else: # Replace the attrib in the class dict with its type. attr.typ = attrib.typ classgen.add_member(node, cls, name, attr.typ) if (attrib.type_source == TypeSource.TYPE and isinstance(cls, abstract.InterpreterClass)): # Add the attrib to the class's __annotations__ dict. annotations_dict = classgen.get_or_create_annotations_dict( cls.members, self.ctx) annotations_dict.annotated_locals[name] = abstract_utils.Local( node, None, attrib.typ, orig, self.ctx) # TODO(mdemello): This will raise a confusing 'annotation-type-mismatch' # error even if the type has been set via the type or converter arg # rather than a type annotation. We need a more general 'type-mismatch' # error to cover overlays that do runtime type checking. We will work # around it with a footnote for now. msg = ("Note: The 'assignment' here is the 'default' or 'factory' arg," " which conflicts with the field type (set via annotation or a" " 'type' or 'converter' arg).") self.ctx.check_annotation_type_mismatch( node, attr.name, attr.typ, attr.default, local.stack, allow_none=True, details=msg) own_attrs.append(attr) elif self.args[cls]["auto_attribs"]: if not match_classvar(typ): self.ctx.check_annotation_type_mismatch( node, name, typ, orig, local.stack, allow_none=True) attr = Attribute( name=name, typ=typ, init=True, kw_only=False, default=orig) if not orig: classgen.add_member(node, cls, name, typ) own_attrs.append(attr) cls.record_attr_ordering(own_attrs) attrs = cls.compute_attr_metadata(own_attrs, "attr.s") # Add an __init__. # If the "init" parameter of decorator is False, instead an # __attrs_init__ function is generated, which is what would have been # generated for __init__ if the init parameter was True. # This logic was added in attrs version 21.1.0 init_method_name = ( "__init__" if self.args[cls]["init"] else "__attrs_init__") init_method = self.make_init(node, cls, attrs, init_method_name) cls.members[init_method_name] = init_method if isinstance(cls, abstract.InterpreterClass): cls.decorators.append("attr.s") # Fix up type parameters in methods added by the decorator. cls.update_method_type_params()
def decorate(self, node, cls): """Processes class members.""" # Collect classvars to convert them to attrs. @dataclass collects vars with # an explicit type annotation, in order of annotation, so that e.g. # class A: # x: int # y: str = 'hello' # x = 10 # would have init(x:int = 10, y:str = 'hello') own_attrs = [] cls_locals = self.get_class_locals(node, cls) for name, local in cls_locals.items(): typ, orig = local.get_type(node, name), local.orig kind = "" assert typ if match_classvar(typ): continue initvar_typ = self._handle_initvar(node, cls, name, typ, orig) if initvar_typ: typ = initvar_typ init = True kind = classgen.AttributeKinds.INITVAR else: if not orig: classgen.add_member(node, cls, name, typ) if is_field(orig): field = orig.data[0] orig = field.default init = field.init else: init = True if orig and orig.data == [self.ctx.convert.none]: # vm._apply_annotation mostly takes care of checking that the default # matches the declared type. However, it allows None defaults, and # dataclasses do not. self.ctx.check_annotation_type_mismatch(node, name, typ, orig, local.stack, allow_none=False) attr = classgen.Attribute(name=name, typ=typ, init=init, kw_only=False, default=orig, kind=kind) own_attrs.append(attr) cls.record_attr_ordering(own_attrs) attrs = cls.compute_attr_metadata(own_attrs, "dataclasses.dataclass") # Add an __init__ method if one doesn't exist already (dataclasses do not # overwrite an explicit __init__ method). if ("__init__" not in cls.members and self.args[cls] and self.args[cls]["init"]): init_method = self.make_init(node, cls, attrs) cls.members["__init__"] = init_method # Add the __dataclass_fields__ attribute, the presence of which # dataclasses.is_dataclass uses to determine if an object is a dataclass (or # an instance of one). attr_types = self.ctx.convert.merge_values( {attr.typ for attr in attrs}) dataclass_ast = self.ctx.loader.import_name("dataclasses") generic_field = abstract.ParameterizedClass( self.ctx.convert.name_to_value("dataclasses.Field", ast=dataclass_ast), {abstract_utils.T: attr_types}, self.ctx) dataclass_fields_params = { abstract_utils.K: self.ctx.convert.str_type, abstract_utils.V: generic_field } dataclass_fields_typ = abstract.ParameterizedClass( self.ctx.convert.dict_type, dataclass_fields_params, self.ctx) classgen.add_member(node, cls, "__dataclass_fields__", dataclass_fields_typ) annotations_dict = classgen.get_or_create_annotations_dict( cls.members, self.ctx) annotations_dict.annotated_locals["__dataclass_fields__"] = ( abstract_utils.Local(node, None, dataclass_fields_typ, None, self.ctx)) if isinstance(cls, abstract.InterpreterClass): cls.decorators.append("dataclasses.dataclass") # Fix up type parameters in methods added by the decorator. cls.update_method_type_params()
def decorate(self, node, cls): """Processes the attrib members of a class.""" # Collect classvars to convert them to attrs. if self.args[cls]["auto_attribs"]: ordering = classgen.Ordering.FIRST_ANNOTATE else: ordering = classgen.Ordering.LAST_ASSIGN ordered_locals = classgen.get_class_locals(cls.name, allow_methods=False, ordering=ordering, vm=self.vm) own_attrs = [] for name, local in ordered_locals.items(): typ, orig = local.get_type(node, name), local.orig if is_attrib(orig): attrib = orig.data[0] if typ and attrib.has_type: # We cannot have both a type annotation and a type argument. self.vm.errorlog.invalid_annotation(self.vm.frames, typ) attr = Attribute(name=name, typ=self.vm.convert.unsolvable, init=attrib.init, kw_only=attrib.kw_only, default=attrib.default) elif not typ: # Replace the attrib in the class dict with its type. attr = Attribute(name=name, typ=attrib.typ, init=attrib.init, kw_only=attrib.kw_only, default=attrib.default) classgen.add_member(node, cls, name, attr.typ) else: # cls.members[name] has already been set via a typecomment attr = Attribute(name=name, typ=typ, init=attrib.init, kw_only=attrib.kw_only, default=attrib.default) self.vm.check_annotation_type_mismatch(node, attr.name, attr.typ, attr.default, local.stack, allow_none=True) own_attrs.append(attr) elif self.args[cls]["auto_attribs"]: if not match_classvar(typ): self.vm.check_annotation_type_mismatch(node, name, typ, orig, local.stack, allow_none=True) attr = Attribute(name=name, typ=typ, init=True, kw_only=False, default=orig) if not orig: classgen.add_member(node, cls, name, typ) own_attrs.append(attr) cls.record_attr_ordering(own_attrs) attrs = cls.compute_attr_metadata(own_attrs, "attr.s") # Add an __init__ method if self.args[cls]["init"]: init_method = self.make_init(node, cls, attrs) cls.members["__init__"] = init_method if isinstance(cls, abstract.InterpreterClass): cls.decorators.append("attr.s") # Fix up type parameters in methods added by the decorator. cls.update_method_type_params()