def make_class(self, node, f_locals): # If BuildClass.call() hits max depth, f_locals will be [unsolvable] # Since we don't support defining NamedTuple subclasses in a nested scope # anyway, we can just return unsolvable here to prevent a crash, and let the # invalid namedtuple error get raised later. if f_locals.data[0].isinstance_Unsolvable(): return node, self.vm.new_unsolvable(node) f_locals = abstract_utils.get_atomic_python_constant(f_locals) # retrieve __qualname__ to get the name of class name = f_locals["__qualname__"] # assemble the arguments that are compatible with NamedTupleFuncBuilder.call field_list = [] defaults = [] cls_locals = classgen.get_class_locals( abstract_utils.get_atomic_python_constant(name), allow_methods=True, ordering=classgen.Ordering.FIRST_ANNOTATE, vm=self.vm) for k, local in cls_locals.items(): assert local.typ if k in f_locals: defaults.append(f_locals[k]) k = self.vm.convert.constant_to_var(k, node=node) field_list.append(self.vm.convert.build_tuple( node, (k, local.typ))) anno = self.vm.convert.build_list(node, field_list) posargs = (name, anno) args = function.Args(posargs=posargs) node, cls_var = self.namedtuple.call(node, None, args) cls_val = abstract_utils.get_atomic_value(cls_var) if not isinstance(cls_val, abstract.Unsolvable): # set __new__.__defaults__ defaults = abstract.Tuple(tuple(defaults), self.vm).to_variable(node) node, new_attr = self.vm.attribute_handler.get_attribute( node, cls_val, "__new__") new_attr = abstract_utils.get_atomic_value(new_attr) node = self.vm.attribute_handler.set_attribute( node, new_attr, "__defaults__", defaults) # set the attribute without overriding special namedtuple attributes node, fields = self.vm.attribute_handler.get_attribute( node, cls_val, "_fields") fields = abstract_utils.get_atomic_python_constant(fields, tuple) fields = [ abstract_utils.get_atomic_python_constant(field, str) for field in fields ] for key in f_locals: if key in self._prohibited: self.vm.errorlog.not_writable(self.vm.frames, cls_val, key) if key not in self._special and key not in fields: node = self.vm.attribute_handler.set_attribute( node, cls_val, key, f_locals[key]) return node, cls_var
def _getargs(self, node, args): self.match_args(node, args) sig, = self.signatures callargs = { name: var for name, var, _ in sig.signature.iter_args(args) } # typing.NamedTuple doesn't support rename or verbose name_var = callargs["typename"] fields_var = callargs["fields"] fields = abstract_utils.get_atomic_python_constant(fields_var) # The fields is a list of tuples, so we need to deeply unwrap them. fields = [abstract_utils.get_atomic_python_constant(t) for t in fields] # We need the actual string for the field names and the AtomicAbstractValue # for the field types. names = [] types = [] for field in fields: if (len(field) != 2 or any(not self._is_str_instance(v) for v in field[0].data)): # Note that we don't need to check field[1] because both 'str' # (forward reference) and 'type' are valid for it. sig, = self.signatures bad_param = function.BadParam(name="fields", expected=self._fields_type) raise function.WrongArgTypes(sig.signature, args, self.vm, bad_param) name, typ = field names.append(abstract_utils.get_atomic_python_constant(name)) types.append(abstract_utils.get_atomic_value(typ)) return name_var, names, types
def _getargs(self, node, args): """Extracts the typename, field_names and rename arguments. collections.namedtuple takes potentially 4 arguments, but we only care about three of them. This function checks the argument count and ensures multiple values aren't passed for 'verbose' and 'rename'. Args: node: The current CFG node. Used by match_args. args: A function.Args object Returns: A tuple containing the typename, field_names and rename arguments passed to this call to collections.namedtuple. Raises: function.FailedFunctionCall: The arguments do not match those needed by the function call. See also: abstract.PyTDFunction.match_args(). abstract_utils.ConversionError: One of the args could not be extracted. Typically occurs if typename or one of the field names is in unicode. """ # abstract.PyTDFunction.match_args checks the args for this call. self.match_args(node, args) # namedtuple only has one signature sig, = self.signatures callargs = {name: var for name, var, _ in sig.signature.iter_args(args)} # The name of the namedtuple class is the first arg (a Variable) # We need the actual Variable later, so we'll just return name_var and # extract the name itself later. name_var = callargs["typename"] # The fields are also a Variable, which stores the field names as Variables. # Extract the list itself, we don't need the wrapper. fields_var = callargs["field_names"] fields = abstract_utils.get_atomic_python_constant(fields_var) # namedtuple fields can be given as a single string, e.g. "a, b, c" or as a # list [Variable('a'), Variable('b'), Variable('c')]. # We just want a list of strings. if isinstance(fields, (bytes, six.text_type)): fields = compat.native_str(fields) field_names = fields.replace(",", " ").split() else: field_names = [abstract_utils.get_atomic_python_constant(f) for f in fields] field_names = [compat.native_str(f) for f in field_names] # namedtuple also takes a "verbose" argument, but we don't care about that. # rename will take any problematic field names and give them a new name. # Like the other args, it's stored as a Variable, but we want just a bool. if callargs.get("rename", None): rename = abstract_utils.get_atomic_python_constant(callargs["rename"]) else: rename = False return name_var, field_names, rename
def make_class(self, node, f_locals): f_locals = abstract_utils.get_atomic_python_constant(f_locals) # retrieve __qualname__ to get the name of class name = f_locals["__qualname__"] # retrieve __annotations__ to get the dict # with key-value pair of (variable, type) anno = f_locals.get("__annotations__", {}) if anno: anno = abstract_utils.get_atomic_value(anno) # assemble the arguments that are compatible with NamedTupleFuncBuilder.call field_list = [] defaults = [] for k, v in anno.items(): if k in f_locals: defaults.append(f_locals.get(k)) # TODO(ahxun): check if the value matches the declared type k = self.vm.convert.constant_to_var(k, node=node) field_list.append(self.vm.convert.build_tuple(node, (k, v))) anno = self.vm.convert.build_list(node, field_list) posargs = (name, anno) args = function.Args(posargs=posargs) node, cls_var = self.namedtuple.call(node, None, args) cls_val = abstract_utils.get_atomic_value(cls_var) if not isinstance(cls_val, abstract.Unsolvable): # set __new__.__defaults__ defaults = abstract.Tuple(tuple(defaults), self.vm).to_variable(node) node, new_attr = self.vm.attribute_handler.get_attribute( node, cls_val, "__new__") new_attr = abstract_utils.get_atomic_value(new_attr) node = self.vm.attribute_handler.set_attribute( node, new_attr, "__defaults__", defaults) # set the attribute without overriding special namedtuple attributes node, fields = self.vm.attribute_handler.get_attribute( node, cls_val, "_fields") fields = abstract_utils.get_atomic_python_constant(fields, tuple) fields = [ abstract_utils.get_atomic_python_constant(field, str) for field in fields ] for key in f_locals: if key in self._prohibited: self.vm.errorlog.not_writable(self.vm.frames, cls_val, key) if key not in self._special and key not in fields: node = self.vm.attribute_handler.set_attribute( node, cls_val, key, f_locals[key]) return node, cls_var
def convert_function_annotations(self, node, raw_annotations): """Convert raw annotations to a {name: annotation} dict.""" if raw_annotations: # {"i": int, "return": str} is stored as (int, str, ("i", "return")) names = abstract_utils.get_atomic_python_constant(raw_annotations[-1]) type_list = raw_annotations[:-1] annotations_list = [] for name, t in zip(names, type_list): name = abstract_utils.get_atomic_python_constant(name) t = self.convert_function_type_annotation(name, t) annotations_list.append((name, t)) return self.convert_annotations_list(node, annotations_list) else: return {}
def assert_type(self, stack, node, var, typ=None): """Check that a variable type matches its expected value.""" types = [ self._print_as_actual_type(b.data) for b in abstract_utils.expand_type_parameter_instances(var.bindings) if node.HasCombination([b]) ] actual = self._join_printed_types(types) # assert_type(x) checks that x is not Any if typ is None: if types == ["Any"] or types == ["typing.Any"]: self.error(stack, f"Asserted type was {actual}") return try: expected = abstract_utils.get_atomic_python_constant(typ, str) except abstract_utils.ConversionError: # NOTE: Converting types to strings is provided as a fallback, but is not # really supported, since there are issues around name resolution. vm = typ.data[0].vm typ = vm.annotations_util.extract_annotation( node, typ, "assert_type", vm.simple_stack()) node, typ = vm.init_class(node, typ) wanted = [ self._print_as_actual_type(b.data) for b in abstract_utils.expand_type_parameter_instances(typ.bindings) if node.HasCombination([b]) ] expected = self._join_printed_types(wanted) if actual != expected: details = f"Expected: {expected}\n Actual: {actual}" self.error(stack, actual, details=details)
def call(self, node, _, args): try: name_var, field_names, field_types = self._getargs(node, args) except abstract_utils.ConversionError: return node, self.vm.new_unsolvable(node) try: name = abstract_utils.get_atomic_python_constant(name_var) except abstract_utils.ConversionError: return node, self.vm.new_unsolvable(node) try: field_names = self._validate_and_rename_args( name, field_names, False) except ValueError as e: self.vm.errorlog.invalid_namedtuple_arg(self.vm.frames, utils.message(e)) return node, self.vm.new_unsolvable(node) annots, late_annots = self.vm.annotations_util.convert_annotations_list( moves.zip(field_names, field_types)) field_types = [ annots.get(field_name, self.vm.convert.unsolvable) for field_name in field_names ] node, cls_var = self._build_namedtuple(name, field_names, field_types, late_annots, node) self.vm.trace_classdef(cls_var) return node, cls_var
def call(self, node, func, args): if len(args.posargs) == 4: self.match_args(node, args) # May raise FailedFunctionCall. cls, name_var, bases_var, class_dict_var = args.posargs try: bases = list( abstract_utils.get_atomic_python_constant(bases_var)) if not bases: bases = [ self.vm.convert.object_type.to_variable( self.vm.root_cfg_node) ] node, variable = self.vm.make_class(node, name_var, bases, class_dict_var, cls) except abstract_utils.ConversionError: pass else: return node, variable elif (args.posargs and self.vm.callself_stack and args.posargs[-1].data == self.vm.callself_stack[-1].data): # We're calling type(self) in an __init__ method. A common pattern for # making a class non-instantiable is: # class Foo: # def __init__(self): # if type(self) is Foo: # raise ... # If we were to return 'Foo', pytype would think that this constructor # can never return. The correct return type is something like # TypeVar(bound=Foo), but we can't introduce a type parameter that isn't # bound to a class or function, so we'll go with Any. self.match_args(node, args) # May raise FailedFunctionCall. return node, self.vm.new_unsolvable(node) return super(TypeNew, self).call(node, func, args)
def _get_constant(self, var, name, arg_type, arg_type_desc=None): try: ret = abstract_utils.get_atomic_python_constant(var, arg_type) except abstract_utils.ConversionError: raise TypeVarError("%s must be %s" % ( name, arg_type_desc or "a constant " + arg_type.__name__)) return ret
def call(self, node, _, args): try: name_var, field_names, field_types = self._getargs(node, args) except abstract_utils.ConversionError: return node, self.vm.convert.unsolvable.to_variable(node) try: name = abstract_utils.get_atomic_python_constant(name_var) except abstract_utils.ConversionError: return node, self.vm.convert.unsolvable.to_variable(node) try: field_names = self._validate_and_rename_args( name, field_names, False) except ValueError as e: self.vm.errorlog.invalid_namedtuple_arg(self.vm.frames, utils.message(e)) return node, self.vm.convert.unsolvable.to_variable(node) annots, late_annots = self.vm.annotations_util.convert_annotations_list( moves.zip(field_names, field_types)) if late_annots: # We currently don't support forward references. Report if we find any, # then continue by using Unsolvable instead. self.vm.errorlog.not_supported_yet( self.vm.frames, "Forward references in typing.NamedTuple") field_types = [ annots.get(field_name, self.vm.convert.unsolvable) for field_name in field_names ] cls_var = self._build_namedtuple(name, field_names, field_types, node) self.vm.trace_classdef(cls_var) return node, cls_var
def starstarargs_as_dict(self): try: args = self.starstarargs and abstract_utils.get_atomic_python_constant( self.starstarargs, dict) except abstract_utils.ConversionError: args = None return args
def call(self, node, func, args): args = args.simplify(node) self.match_args(node, args, match_all_views=True) # As long as the types match we do not really care about the actual # class name. But, if we have a string literal value as the name arg, # we will use it. name_arg = args.namedargs.get(self._name_arg_name) or args.posargs[0] try: _ = abstract_utils.get_atomic_python_constant(name_arg, str) except abstract_utils.ConversionError: name_arg = self.vm.convert.constant_to_var( "_NewType_Internal_Class_Name_%d_" % self.internal_name_counter) type_arg = args.namedargs.get(self._type_arg_name) or args.posargs[1] try: type_value = abstract_utils.get_atomic_value(type_arg) except abstract_utils.ConversionError: # We need the type arg to be an atomic value. If not, we just # silently return unsolvable. return node, self.vm.new_unsolvable(node) value_arg_name = "val" constructor = overlay_utils.make_method( self.vm, node, name="__init__", params=[Param(value_arg_name, type_value)]) members = abstract.Dict(self.vm) members.set_str_item(node, "__init__", constructor) return self.vm.make_class(node, name_arg, (type_arg, ), members.to_variable(node), None)
def build_slice(self, node, start, stop, step=None): const_types = (int, type(None)) try: if start: start = abstract_utils.get_atomic_python_constant( start, const_types) if stop: stop = abstract_utils.get_atomic_python_constant( stop, const_types) if step: step = abstract_utils.get_atomic_python_constant( step, const_types) except abstract_utils.ConversionError: return self.primitive_class_instances[slice].to_variable(node) return abstract.AbstractOrConcreteValue(slice(start, stop, step), self.primitive_classes[slice], self.vm).to_variable(node)
def _update_kwargs(self, args): for k, v in args.namedargs.items(): if k in self.args: try: self.args[k] = abstract_utils.get_atomic_python_constant(v) except abstract_utils.ConversionError: self.vm.errorlog.not_supported_yet( self.vm.frames, "Non-constant attr.s argument %r" % k)
def get_kwarg(self, args, name, default): if name not in args.namedargs: return default try: return abstract_utils.get_atomic_python_constant(args.namedargs[name]) except abstract_utils.ConversionError: self.vm.errorlog.not_supported_yet( self.vm.frames, "Non-constant argument %r" % name)
def init_from_annotations(self, node, name, annots_var): """Instantiate `name` from the given annotations dict, calling __init__.""" try: annots = abstract_utils.get_atomic_python_constant(annots_var, dict) except abstract_utils.ConversionError: return None if name not in annots: return None return self.init_annotation_var(node, name, annots[name])
def update_kwargs(self, args): self._current_args = Decorator._DEFAULT_ARGS.copy() for k, v in args.namedargs.items(): if k in self._current_args: try: self._current_args[k] = abstract_utils.get_atomic_python_constant(v) except abstract_utils.ConversionError: self.vm.errorlog.not_supported_yet( self.vm.frames, "Non-constant argument to decorator: %r" % k)
def get_annotations_dict(self, members): """Get __annotations__ from a members map.""" if "__annotations__" not in members: return {} annots_var = members["__annotations__"] try: annots = abstract_utils.get_atomic_python_constant(annots_var, dict) except abstract_utils.ConversionError: return {} return annots
def starargs_as_tuple(self, node, vm): try: args = self.starargs and abstract_utils.get_atomic_python_constant( self.starargs, tuple) except abstract_utils.ConversionError: args = None if not args: return args return tuple(var if var.bindings else vm.convert.empty.to_variable(node) for var in args)
def _get_constant_tuple_prefix(value: abstract.Tuple): """Given a tuple, get its longest prefix of constant elements.""" elements = [] for element_var in value.pyval: try: element = abstract_utils.get_atomic_python_constant( element_var, tuple(value.vm.convert.primitive_classes)) except abstract_utils.ConversionError: return tuple(elements) elements.append(element) return tuple(elements)
def _get_annotation(self, var, name): try: ret = abstract_utils.get_atomic_value(var, self._CLASS_TYPE) if isinstance(ret, abstract.AbstractOrConcreteValue): ret = abstract_utils.get_atomic_python_constant( var, six.string_types) except abstract_utils.ConversionError: raise TypeVarError("%s must be constant" % name) if not ret: raise TypeVarError("%s cannot be an empty string" % name) return ret
def _getargs(self, node, args): self.match_args(node, args) sig, = self.signatures callargs = { name: var for name, var, _ in sig.signature.iter_args(args) } # typing.NamedTuple doesn't support rename or verbose name_var = callargs["typename"] fields_var = callargs["fields"] fields = abstract_utils.get_atomic_python_constant(fields_var) if isinstance(fields, six.string_types): # Since str matches Sequence, we have to manually check for it. raise function.WrongArgTypes(sig.signature, args, self.vm, self._fields_param) # The fields is a list of tuples, so we need to deeply unwrap them. fields = [abstract_utils.get_atomic_python_constant(t) for t in fields] # We need the actual string for the field names and the AtomicAbstractValue # for the field types. names = [] types = [] for field in fields: if isinstance(field, six.string_types): # Since str matches Sequence, we have to manually check for it. raise function.WrongArgTypes(sig.signature, args, self.vm, self._fields_param) if (len(field) != 2 or any(not self._is_str_instance(v) for v in field[0].data)): # Note that we don't need to check field[1] because both 'str' # (forward reference) and 'type' are valid for it. raise function.WrongArgTypes(sig.signature, args, self.vm, self._fields_param) name, typ = field name_py_constant = abstract_utils.get_atomic_python_constant(name) if name_py_constant.__class__ is compat.UnicodeType: # Unicode values should be ASCII. name_py_constant = compat.native_str( name_py_constant.encode("ascii")) names.append(name_py_constant) types.append(abstract_utils.get_atomic_value(typ)) return name_var, names, types
def call(self, node, func, args): if len(args.posargs) == 4: self.match_args(node, args) # May raise FailedFunctionCall. cls, name_var, bases_var, class_dict_var = args.posargs try: bases = list( abstract_utils.get_atomic_python_constant(bases_var)) if not bases: bases = [ self.vm.convert.object_type.to_variable( self.vm.root_node) ] node, variable = self.vm.make_class(node, name_var, bases, class_dict_var, cls) except abstract_utils.ConversionError: pass else: return node, variable elif (args.posargs and self.vm.callself_stack and args.posargs[-1].data == self.vm.callself_stack[-1].data): # We're calling type(self) in an __init__ method. A common pattern for # making a class non-instantiable is: # class Foo: # def __init__(self): # if type(self) is Foo: # raise ... # If we were to return 'Foo', pytype would think that this constructor # can never return. The correct return type is something like # TypeVar(bound=Foo), but we can't introduce a type parameter that isn't # bound to a class or function, so we'll go with Any. self.match_args(node, args) # May raise FailedFunctionCall. return node, self.vm.new_unsolvable(node) elif args.posargs and all(v.full_name == "typing.Protocol" for v in args.posargs[-1].data): # type(Protocol) is a _ProtocolMeta class that inherits from abc.ABCMeta. # Changing the definition of Protocol in typing.pytd to include this # metaclass causes a bunch of weird breakages, so we instead return the # metaclass when type() or __class__ is accessed on Protocol. For # simplicity, we pretend the metaclass is ABCMeta rather than a subclass. self.match_args(node, args) # May raise FailedFunctionCall. abc = self.vm.import_module("abc", "abc", 0).get_module("ABCMeta") abc.load_lazy_attribute("ABCMeta") return node, abc.members["ABCMeta"].AssignToNewVariable(node) node, raw_ret = super().call(node, func, args) # Removes TypeVars from the return value. # See test_typevar.TypeVarTest.test_type_of_typevar(_error). ret = self.vm.program.NewVariable() for b in raw_ret.bindings: value = self.vm.annotations_util.deformalize(b.data) ret.AddBinding(value, {b}, node) return node, ret
def call(self, node, func, args): if self.vm.PY3: # In Python 3, the type of IO object returned depends on the mode. self.match_args(node, args) # May raise FailedFunctionCall. sig, = self.signatures callargs = {name: var for name, var, _ in sig.signature.iter_args(args)} try: if "mode" not in callargs: io_type = "Text" # The default mode is 'r'. else: mode = abstract_utils.get_atomic_python_constant(callargs["mode"]) io_type = "Binary" if "b" in mode else "Text" except abstract_utils.ConversionError: pass else: return node, self.vm.convert.constant_to_var(abstract_utils.AsInstance( self.vm.lookup_builtin("typing.%sIO" % io_type)), {}, node) return super(Open, self).call(node, func, args)
def _get_class_or_constant(self, var, name, arg_type, arg_type_desc=None): if arg_type is self._CLASS_TYPE: convert_func = abstract_utils.get_atomic_value type_desc = arg_type_desc or "an unambiguous type" else: convert_func = abstract_utils.get_atomic_python_constant type_desc = arg_type_desc or "a constant " + arg_type.__name__ try: ret = convert_func(var, arg_type) # If we have a class type as an AbstractOrConcreteValue, we want to return # it as a string. if isinstance(ret, abstract.AbstractOrConcreteValue): ret = abstract_utils.get_atomic_python_constant(var, str) if not ret: raise TypeVarError("%s cannot be an empty string" % name) return ret except abstract_utils.ConversionError: raise TypeVarError("%s must be %s" % (name, type_desc))
def call(self, node, _, args, alias_map=None): _, argmap = self.match_and_map_args(node, args, alias_map) cls_var = argmap["cls"] name_var = argmap["name"] try: cls = abstract_utils.get_atomic_value(cls_var) except abstract_utils.ConversionError: return node, self.vm.new_unsolvable(node) # If we can't get a concrete name, treat it like it matches and return a # canonical enum member. try: name = abstract_utils.get_atomic_python_constant(name_var, str) except abstract_utils.ConversionError: return node, cls.instantiate(node) inst = self._get_member_by_name(cls, name) if inst: return node, inst else: self.vm.errorlog.attribute_error(self.vm.frames, cls_var.bindings[0], name) return node, self.vm.new_unsolvable(node)
def annotations_to_instance_types(self, annotations_var): """Convert the members of an __annotations__ dict to instance types. Args: annotations_var: __annotations__, a cfg.Variable of an abstract.Dict. Yields: A tuple of member name and pytd types. """ try: annots = abstract_utils.get_atomic_python_constant( annotations_var, dict) except abstract_utils.ConversionError: return for name, member in annots.items(): yield name, [ self.value_instance_to_pytd_type(node=None, v=v, instance=None, seen=None, view=None) for v in member.data ]
def get_file_mode(sig, args): callargs = {name: var for name, var, _ in sig.signature.iter_args(args)} if "mode" in callargs: return abstract_utils.get_atomic_python_constant(callargs["mode"]) else: return ""
def call(self, node, _, args): """Creates a namedtuple class definition. Performs the same argument checking as collections.namedtuple, e.g. making sure field names don't start with _ or digits, making sure no keywords are used for the typename or field names, and so on. Because the methods of the class have to be changed to match the number and names of the fields, we construct pytd.Function and pytd.Constant instances for each member of the class. Finally, the pytd.Class is wrapped in an abstract.PyTDClass. If incorrect arguments are passed, a subclass of function.FailedFunctionCall will be raised. Other cases may raise abstract_utils.ConversionError exceptions, such as when the arguments are in unicode or any of the arguments have multiple bindings, but these are caught and return Any. This also occurs if an argument to namedtuple is invalid, e.g. a keyword is used as a field name and rename is False. Arguments: node: the current CFG node _: the func binding, ignored. args: a function.Args instance Returns: a tuple of the given CFG node and an abstract.PyTDClass instance (wrapped in a Variable) representing the constructed namedtuple class. If a abstract_utils.ConversionError occurs or if field names are invalid, this function returns Unsolvable (in a Variable) instead of a PyTDClass. Raises: function.FailedFunctionCall: Raised by _getargs if any of the arguments do not match the function signature. """ # If we can't extract the arguments, we take the easy way out and return Any try: name_var, field_names, rename = self._getargs(node, args) except abstract_utils.ConversionError: return node, self.vm.new_unsolvable(node) # We need the bare name for a few things, so pull that out now. # The same unicode issue can strike here, so again return Any. try: name = abstract_utils.get_atomic_python_constant(name_var) except abstract_utils.ConversionError: return node, self.vm.new_unsolvable(node) # namedtuple does some checking and optionally renaming of field names, # so we do too. try: field_names = self._validate_and_rename_args( name, field_names, rename) except ValueError as e: self.vm.errorlog.invalid_namedtuple_arg(self.vm.frames, utils.message(e)) return node, self.vm.new_unsolvable(node) name = namedtuple_name(name, field_names) ast = namedtuple_ast(name, field_names, python_version=self.vm.python_version) mapping = self._get_known_types_mapping() # A truly well-formed pyi for the namedtuple will have references to the new # namedtuple class itself for all `self` params and as the return type for # methods like __new__, _replace and _make. In order to do that, we need a # ClassType. cls_type = pytd.ClassType(name) mapping[name] = cls_type cls = ast.Lookup(name).Visit(visitors.ReplaceTypes(mapping)) cls_type.cls = cls # Make sure all NamedType nodes have been replaced by ClassType nodes with # filled cls pointers. cls.Visit(visitors.VerifyLookup()) # We can't build the PyTDClass directly, and instead must run it through # convert.constant_to_value first, for caching. instance = self.vm.convert.constant_to_value(cls, {}, self.vm.root_cfg_node) self.vm.trace_namedtuple(instance) return node, instance.to_variable(node)
def call(self, node, func, args, alias_map=None): """Implements the behavior of the enum functional API.""" # Because of how this is called, we supply our own "self" argument. # See class_mixin.Class._call_new_and_init. args = args.simplify(node, self.vm) args = args.replace(posargs=(self.vm.new_unsolvable(node), ) + args.posargs) self.load_lazy_attribute("__new__") new = abstract_utils.get_atomic_value(self.members["__new__"]) # Note that super().call or _call_new_and_init won't work here, because # they don't raise FailedFunctionCall. new.match_args(node, args, alias_map) # There should only be 1 signature for Enum.__new__. assert len( new.signatures) == 1, "Expected only 1 Enum.__new__ signature." sig = new.signatures[0].signature argmap = {name: var for name, var, _ in sig.iter_args(args)} cls_name_var = argmap["value"] try: names = abstract_utils.get_atomic_python_constant(argmap["names"]) except abstract_utils.ConversionError as e: log.info( "Failed to unwrap values in enum functional interface:\n%s", e) return node, self.vm.new_unsolvable(node) if isinstance(names, str): names = names.replace(",", " ").split() fields = {name: self.vm.convert.build_int(node) for name in names} elif isinstance(names, dict): # Dict keys are strings, not strings in variables. The values are # variables, they don't need to be changed. fields = names else: # List of names, or list of (name, value) pairs. try: possible_pairs = [ abstract_utils.get_atomic_python_constant(p) for p in names ] except abstract_utils.ConversionError as e: log.debug("Failed to unwrap possible enum field pairs:\n %s", e) return node, self.vm.new_unsolvable(node) if not possible_pairs: fields = {} elif isinstance(possible_pairs[0], str): fields = { name: self.vm.convert.build_int(node) for name in possible_pairs } else: # List of (name_var, value_var) pairs. # The earlier get_atomic_python_constant call only unwrapped the tuple, # so the values in the tuple still need to be unwrapped. try: fields = { abstract_utils.get_atomic_python_constant(name): value for name, value in possible_pairs } except abstract_utils.ConversionError as e: log.debug("Failed to unwrap field names for enum:\n %s", e) return node, self.vm.new_unsolvable(node) cls_dict = abstract.Dict(self.vm) cls_dict.update(node, fields) metaclass = self.vm.loaded_overlays["enum"].members["EnumMeta"] return self.vm.make_class(node=node, name_var=cls_name_var, bases=[self.to_variable(node)], class_dict_var=cls_dict.to_variable(node), cls_var=metaclass, class_type=EnumInstance)