Exemplo n.º 1
0
def make_replace_method(vm, node, cls, *, kwargs_name="kwargs"):
    """Create a replace() method for a dataclass."""
    # This is used by several packages that extend dataclass.
    # The signature is
    #   def replace(self: T, **kwargs) -> T
    typevar = abstract.TypeParameter(abstract_utils.T + cls.name,
                                     vm,
                                     bound=cls)
    return overlay_utils.make_method(
        vm=vm,
        node=node,
        name="replace",
        return_type=typevar,
        self_param=overlay_utils.Param("self", typevar),
        kwargs=overlay_utils.Param(kwargs_name),
    )
Exemplo n.º 2
0
    def _make_init(self, node, attrs):
        attr_params = []
        for attr in attrs:
            if attr.init:
                # attrs removes leading underscores from attrib names when
                # generating kwargs for __init__.
                attr_params.append(
                    Param(name=attr.name.lstrip("_"),
                          typ=attr.typ,
                          default=attr.default))

        # The kw_only arg is ignored in python2; using it is not an error.
        if self.args["kw_only"] and self.vm.PY3:
            params = []
            kwonly_params = attr_params
        else:
            params = attr_params
            kwonly_params = []

        return overlay_utils.make_method(self.vm, node, "__init__", params,
                                         kwonly_params)
Exemplo n.º 3
0
    def make_init(self, node, cls, attrs):
        attr_params = []
        for attr in attrs:
            if attr.init:
                # call self.init_name in case the name differs from the field name -
                # e.g. attrs removes leading underscores from attrib names when
                # generating kwargs for __init__.
                attr_params.append(
                    Param(name=self.init_name(attr),
                          typ=attr.typ,
                          default=attr.default))

        # The kw_only arg is ignored in python2; using it is not an error.
        if self.args[cls]["kw_only"] and self.vm.PY3:
            params = []
            kwonly_params = attr_params
        else:
            params = attr_params
            kwonly_params = []

        return overlay_utils.make_method(self.vm, node, "__init__", params,
                                         kwonly_params)
Exemplo n.º 4
0
    def make_init(self, node, cls, attrs, init_method_name="__init__"):
        pos_params = []
        kwonly_params = []
        all_kwonly = self.args[cls]["kw_only"]
        for attr in attrs:
            if not attr.init:
                continue
            typ = attr.init_type or attr.typ
            # call self.init_name in case the name differs from the field name -
            # e.g. attrs removes leading underscores from attrib names when
            # generating kwargs for __init__.
            param = Param(name=self.init_name(attr),
                          typ=typ,
                          default=attr.default)

            # kw_only=False in a field does not override kw_only=True in the class.
            if all_kwonly or attr.kw_only:
                kwonly_params.append(param)
            else:
                pos_params.append(param)

        return overlay_utils.make_method(self.ctx, node, init_method_name,
                                         pos_params, 0, kwonly_params)
Exemplo n.º 5
0
    def make_init(self, node, cls, attrs):
        pos_params = []
        kwonly_params = []
        all_kwonly = self.args[cls]["kw_only"]
        for attr in attrs:
            if not attr.init:
                continue
            # call self.init_name in case the name differs from the field name -
            # e.g. attrs removes leading underscores from attrib names when
            # generating kwargs for __init__.
            param = Param(name=self.init_name(attr),
                          typ=attr.typ,
                          default=attr.default)

            # The kw_only arg is ignored in python2; using it is not an error.
            # kw_only=False in a field does not override kw_only=True in the class.
            if self.vm.PY3 and (all_kwonly or attr.kw_only):
                kwonly_params.append(param)
            else:
                pos_params.append(param)

        return overlay_utils.make_method(self.vm, node, "__init__", pos_params,
                                         kwonly_params)
Exemplo n.º 6
0
    def _build_namedtuple(self, name, field_names, field_types, late_annots,
                          node):
        # Build an InterpreterClass representing the namedtuple.
        if field_types:
            # TODO(mdemello): Fix this to support late types.
            field_types_union = abstract.Union(field_types, self.vm)
        else:
            field_types_union = self.vm.convert.none_type

        members = {
            n: t.instantiate(node)
            for n, t in moves.zip(field_names, field_types)
        }

        # collections.namedtuple has: __dict__, __slots__ and _fields.
        # typing.NamedTuple adds: _field_types, __annotations__ and _field_defaults.
        # __slots__ and _fields are tuples containing the names of the fields.
        slots = tuple(
            self.vm.convert.build_string(node, f) for f in field_names)
        members["__slots__"] = abstract.Tuple(slots, self.vm).to_variable(node)
        members["_fields"] = abstract.Tuple(slots, self.vm).to_variable(node)
        # __dict__ and _field_defaults are both collections.OrderedDicts that map
        # field names (strings) to objects of the field types.
        ordered_dict_cls = self.vm.convert.name_to_value(
            "collections.OrderedDict", ast=self.collections_ast)

        # In Python 2, keys can be `str` or `unicode`; support both.
        # In Python 3, `str_type` and `unicode_type` are the same.
        field_keys_union = abstract.Union(
            [self.vm.convert.str_type, self.vm.convert.unicode_type], self.vm)

        # Normally, we would use abstract_utils.K and abstract_utils.V, but
        # collections.pyi doesn't conform to that standard.
        field_dict_cls = abstract.ParameterizedClass(ordered_dict_cls, {
            "K": field_keys_union,
            "V": field_types_union
        }, self.vm)
        members["__dict__"] = field_dict_cls.instantiate(node)
        members["_field_defaults"] = field_dict_cls.instantiate(node)
        # _field_types and __annotations__ are both collections.OrderedDicts
        # that map field names (strings) to the types of the fields.
        field_types_cls = abstract.ParameterizedClass(
            ordered_dict_cls, {
                "K": field_keys_union,
                "V": self.vm.convert.type_type
            }, self.vm)
        members["_field_types"] = field_types_cls.instantiate(node)
        members["__annotations__"] = field_types_cls.instantiate(node)

        # __new__
        # We set the bound on this TypeParameter later. This gives __new__ the
        # signature: def __new__(cls: Type[_Tname], ...) -> _Tname, i.e. the same
        # signature that visitor.CreateTypeParametersForSignatures would create.
        # This allows subclasses of the NamedTuple to get the correct type from
        # their constructors.
        cls_type_param = abstract.TypeParameter(
            visitors.CreateTypeParametersForSignatures.PREFIX + name,
            self.vm,
            bound=None)
        cls_type = abstract.ParameterizedClass(
            self.vm.convert.type_type, {abstract_utils.T: cls_type_param},
            self.vm)
        # Use late annotations as field types if they exist.
        params = [
            Param(n, late_annots.get(n, t))
            for n, t in moves.zip(field_names, field_types)
        ]
        members["__new__"] = overlay_utils.make_method(
            self.vm,
            node,
            name="__new__",
            self_param=Param("cls", cls_type),
            params=params,
            return_type=cls_type_param,
        )

        # __init__
        members["__init__"] = overlay_utils.make_method(self.vm,
                                                        node,
                                                        name="__init__",
                                                        varargs=Param("args"),
                                                        kwargs=Param("kwargs"))

        # _make
        # _make is a classmethod, so it needs to be wrapped by
        # specialibuiltins.ClassMethodInstance.
        # Like __new__, it uses the _Tname TypeVar.
        sized_cls = self.vm.convert.name_to_value("typing.Sized")
        iterable_type = abstract.ParameterizedClass(
            self.vm.convert.name_to_value("typing.Iterable"),
            {abstract_utils.T: field_types_union}, self.vm)
        cls_type = abstract.ParameterizedClass(
            self.vm.convert.type_type, {abstract_utils.T: cls_type_param},
            self.vm)
        len_type = abstract.CallableClass(
            self.vm.convert.name_to_value("typing.Callable"), {
                0: sized_cls,
                abstract_utils.ARGS: sized_cls,
                abstract_utils.RET: self.vm.convert.int_type
            }, self.vm)
        params = [
            Param("iterable", iterable_type),
            Param("new").unsolvable(self.vm, node),
            Param("len", len_type).unsolvable(self.vm, node)
        ]
        make = overlay_utils.make_method(self.vm,
                                         node,
                                         name="_make",
                                         params=params,
                                         self_param=Param("cls", cls_type),
                                         return_type=cls_type_param)
        make_args = function.Args(posargs=(make, ))
        _, members["_make"] = self.vm.special_builtins["classmethod"].call(
            node, None, make_args)

        # _replace
        # Like __new__, it uses the _Tname TypeVar. We have to annotate the `self`
        # param to make sure the TypeVar is substituted correctly.
        members["_replace"] = overlay_utils.make_method(
            self.vm,
            node,
            name="_replace",
            self_param=Param("self", cls_type_param),
            return_type=cls_type_param,
            kwargs=Param("kwds", field_types_union))

        # __getnewargs__
        getnewargs_tuple_params = dict(
            tuple(enumerate(field_types)) +
            ((abstract_utils.T, field_types_union), ))
        getnewargs_tuple = abstract.TupleClass(self.vm.convert.tuple_type,
                                               getnewargs_tuple_params,
                                               self.vm)
        members["__getnewargs__"] = overlay_utils.make_method(
            self.vm, node, name="__getnewargs__", return_type=getnewargs_tuple)

        # __getstate__
        members["__getstate__"] = overlay_utils.make_method(
            self.vm, node, name="__getstate__")

        # _asdict
        members["_asdict"] = overlay_utils.make_method(
            self.vm, node, name="_asdict", return_type=field_dict_cls)

        # Finally, make the class.
        cls_dict = abstract.Dict(self.vm)
        cls_dict.update(node, members)
        if name.__class__ is compat.UnicodeType:
            # Unicode values should be ASCII.
            name = compat.native_str(name.encode("ascii"))

        node, cls_var = self.vm.make_class(
            node=node,
            name_var=self.vm.convert.build_string(node, name),
            bases=[self.vm.convert.tuple_type.to_variable(node)],
            class_dict_var=cls_dict.to_variable(node),
            cls_var=None)
        cls = cls_var.data[0]

        # Now that the class has been made, we can complete the TypeParameter used
        # by __new__, _make and _replace.
        cls_type_param.bound = cls

        # Add late annotations to the new class
        if late_annots:
            cls.late_annotations = late_annots
            self.vm.classes_with_late_annotations.append(cls)

        return node, cls_var
Exemplo n.º 7
0
  def _build_namedtuple(self, name, field_names, field_types, node, bases):
    # Build an InterpreterClass representing the namedtuple.
    if field_types:
      # TODO(mdemello): Fix this to support late types.
      field_types_union = abstract.Union(field_types, self.ctx)
    else:
      field_types_union = self.ctx.convert.none_type

    members = {n: t.instantiate(node) for n, t in zip(field_names, field_types)}

    # collections.namedtuple has: __dict__, __slots__ and _fields.
    # typing.NamedTuple adds: _field_types, __annotations__ and _field_defaults.
    # __slots__ and _fields are tuples containing the names of the fields.
    slots = tuple(self.ctx.convert.build_string(node, f) for f in field_names)
    members["__slots__"] = self.ctx.convert.build_tuple(node, slots)
    members["_fields"] = self.ctx.convert.build_tuple(node, slots)
    # __dict__ and _field_defaults are both collections.OrderedDicts that map
    # field names (strings) to objects of the field types.
    ordered_dict_cls = self.ctx.convert.name_to_value(
        "collections.OrderedDict", ast=self.collections_ast)

    # Normally, we would use abstract_utils.K and abstract_utils.V, but
    # collections.pyi doesn't conform to that standard.
    field_dict_cls = abstract.ParameterizedClass(ordered_dict_cls, {
        "K": self.ctx.convert.str_type,
        "V": field_types_union
    }, self.ctx)
    members["__dict__"] = field_dict_cls.instantiate(node)
    members["_field_defaults"] = field_dict_cls.instantiate(node)
    # _field_types and __annotations__ are both collections.OrderedDicts
    # that map field names (strings) to the types of the fields. Note that
    # ctx.make_class will take care of adding the __annotations__ member.
    field_types_cls = abstract.ParameterizedClass(ordered_dict_cls, {
        "K": self.ctx.convert.str_type,
        "V": self.ctx.convert.type_type
    }, self.ctx)
    members["_field_types"] = field_types_cls.instantiate(node)

    # __new__
    # We set the bound on this TypeParameter later. This gives __new__ the
    # signature: def __new__(cls: Type[_Tname], ...) -> _Tname, i.e. the same
    # signature that visitor.CreateTypeParametersForSignatures would create.
    # This allows subclasses of the NamedTuple to get the correct type from
    # their constructors.
    cls_type_param = abstract.TypeParameter(
        visitors.CreateTypeParametersForSignatures.PREFIX + name,
        self.ctx,
        bound=None)
    cls_type = abstract.ParameterizedClass(self.ctx.convert.type_type,
                                           {abstract_utils.T: cls_type_param},
                                           self.ctx)
    params = [Param(n, t) for n, t in zip(field_names, field_types)]
    members["__new__"] = overlay_utils.make_method(
        self.ctx,
        node,
        name="__new__",
        self_param=Param("cls", cls_type),
        params=params,
        return_type=cls_type_param,
    )

    # __init__
    members["__init__"] = overlay_utils.make_method(
        self.ctx,
        node,
        name="__init__",
        varargs=Param("args"),
        kwargs=Param("kwargs"))

    heterogeneous_tuple_type_params = dict(enumerate(field_types))
    heterogeneous_tuple_type_params[abstract_utils.T] = field_types_union
    # Representation of the to-be-created NamedTuple as a typing.Tuple.
    heterogeneous_tuple_type = abstract.TupleClass(
        self.ctx.convert.tuple_type, heterogeneous_tuple_type_params, self.ctx)

    # _make
    # _make is a classmethod, so it needs to be wrapped by
    # special_builtins.ClassMethodInstance.
    # Like __new__, it uses the _Tname TypeVar.
    sized_cls = self.ctx.convert.name_to_value("typing.Sized")
    iterable_type = abstract.ParameterizedClass(
        self.ctx.convert.name_to_value("typing.Iterable"),
        {abstract_utils.T: field_types_union}, self.ctx)
    cls_type = abstract.ParameterizedClass(self.ctx.convert.type_type,
                                           {abstract_utils.T: cls_type_param},
                                           self.ctx)
    len_type = abstract.CallableClass(
        self.ctx.convert.name_to_value("typing.Callable"), {
            0: sized_cls,
            abstract_utils.ARGS: sized_cls,
            abstract_utils.RET: self.ctx.convert.int_type
        }, self.ctx)
    params = [
        Param("iterable", iterable_type),
        Param("new").unsolvable(self.ctx, node),
        Param("len", len_type).unsolvable(self.ctx, node)
    ]
    make = overlay_utils.make_method(
        self.ctx,
        node,
        name="_make",
        params=params,
        self_param=Param("cls", cls_type),
        return_type=cls_type_param)
    make_args = function.Args(posargs=(make,))
    _, members["_make"] = self.ctx.special_builtins["classmethod"].call(
        node, None, make_args)

    # _replace
    # Like __new__, it uses the _Tname TypeVar. We have to annotate the `self`
    # param to make sure the TypeVar is substituted correctly.
    members["_replace"] = overlay_utils.make_method(
        self.ctx,
        node,
        name="_replace",
        self_param=Param("self", cls_type_param),
        return_type=cls_type_param,
        kwargs=Param("kwds", field_types_union))

    # __getnewargs__
    members["__getnewargs__"] = overlay_utils.make_method(
        self.ctx,
        node,
        name="__getnewargs__",
        return_type=heterogeneous_tuple_type)

    # __getstate__
    members["__getstate__"] = overlay_utils.make_method(
        self.ctx, node, name="__getstate__")

    # _asdict
    members["_asdict"] = overlay_utils.make_method(
        self.ctx, node, name="_asdict", return_type=field_dict_cls)

    # Finally, make the class.
    cls_dict = abstract.Dict(self.ctx)
    cls_dict.update(node, members)

    if self.ctx.options.strict_namedtuple_checks:
      # Enforces type checking like Tuple[...]
      superclass_of_new_type = heterogeneous_tuple_type.to_variable(node)
    else:
      superclass_of_new_type = self.ctx.convert.tuple_type.to_variable(node)
    if bases:
      final_bases = []
      for base in bases:
        if any(b.full_name == "typing.NamedTuple" for b in base.data):
          final_bases.append(superclass_of_new_type)
        else:
          final_bases.append(base)
    else:
      final_bases = [superclass_of_new_type]
      # This NamedTuple is being created via a function call. We manually
      # construct an annotated_locals entry for it so that __annotations__ is
      # initialized properly for the generated class.
      self.ctx.vm.annotated_locals[name] = {
          field: abstract_utils.Local(node, None, typ, None, self.ctx)
          for field, typ in zip(field_names, field_types)
      }

    node, cls_var = self.ctx.make_class(
        node=node,
        name_var=self.ctx.convert.build_string(node, name),
        bases=final_bases,
        class_dict_var=cls_dict.to_variable(node),
        cls_var=None)
    cls = cls_var.data[0]

    # Now that the class has been made, we can complete the TypeParameter used
    # by __new__, _make and _replace.
    cls_type_param.bound = cls

    return node, cls_var