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), )
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)
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)
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)
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)
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
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