示例#1
0
 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
示例#2
0
    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()
示例#3
0
  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()
示例#4
0
    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()
示例#5
0
    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()