def create_method_arguments(self, node, method): """Create arguments for the given method. Args: node: The current node. method: An abstract.InterpreterFunction. Returns: A tuple of a node and an abstract.FunctionArgs object. """ args = [] for i in range(method.argcount(node)): node, arg = self.create_argument(node, method.signature, method.signature.param_names[i]) args.append(arg) kws = {} for key in method.signature.kwonly_params: node, arg = self.create_argument(node, method.signature, key) kws[key] = arg starargs = self.create_varargs(node) if method.has_varargs() else None starstarargs = self.create_kwargs( node) if method.has_kwargs() else None return node, abstract.FunctionArgs(posargs=tuple(args), namedargs=kws, starargs=starargs, starstarargs=starstarargs)
def _class_getter(self, node, cls, name, valself, valcls, skip=None): """Retrieve an attribute by looking at the MRO of this class.""" attr = self._lookup_from_mro(node, cls, name, valself, valcls, skip) if not attr.bindings: return node, None if isinstance(cls, abstract.InterpreterClass): result = self.vm.program.NewVariable() nodes = [] # Deal with descriptors as a potential additional level of indirection. for v in attr.bindings: value = v.data node2, getter = self.get_attribute(node, value, "__get__", v) if getter is not None: posargs = [] if valself: posargs.append(valself.AssignToNewVariable()) if valcls: if not valself: posargs.append( self.vm.convert.none.to_variable(node)) posargs.append(valcls.AssignToNewVariable()) node2, get_result = self.vm.call_function( node2, getter, abstract.FunctionArgs(tuple(posargs))) for getter in get_result.bindings: result.AddBinding(getter.data, [getter], node2) else: result.AddBinding(value, [v], node2) nodes.append(node2) if nodes: return self.vm.join_cfg_nodes(nodes), result return node, attr
def create_method_arguments(self, node, method): """Create arguments for the given method. Creates Unknown objects as arguments for the given method. Note that we don't need to take parameter annotations into account as InterpreterFunction.call() will take care of that. Args: node: The current node. method: An abstract.InterpreterFunction. Returns: A tuple of a node and an abstract.FunctionArgs object. """ args = [ self.convert.create_new_unknown(node, force=True) for _ in range(method.argcount(node)) ] kws = { key: self.convert.create_new_unknown(node, force=True) for key in method.signature.kwonly_params } starargs = self.create_varargs(node) if method.has_varargs() else None starstarargs = self.create_kwargs( node) if method.has_kwargs() else None return node, abstract.FunctionArgs(posargs=tuple(args), namedargs=kws, starargs=starargs, starstarargs=starstarargs)
def assert_call(self, expected, left, right): """Check that call() returned the desired results. Args: expected: A dict from values to source sets, where a source set is represented by the sorted binding names separated by spaces, for example "left:0 right:1" would indicate binding #0 of variable "left" and binding #1 of variable "right". left: A Variable to use as the first arg to call(). right: A Variable to use as the second arg to call(). """ name_map = {left: "left", right: "right"} node, result = self._is_instance.call( self._node, None, abstract.FunctionArgs((left, right), self.new_dict(), None, None)) self.assertIn(node, self._node.outgoing) result_map = {} # Turning source sets into canonical string representations of the binding # names makes it much easier to debug failures. for b in result.bindings: terms = set() for o in b.origins: self.assertEqual(node, o.where) for sources in o.source_sets: terms.add(" ".join( sorted("%s:%d" % (name_map[b.variable], b.variable.bindings.index(b)) for b in sources))) result_map[b.data] = terms self.assertEqual(expected, result_map)
def call(self, node, _, args): self.match_args(node, args) arg = args.posargs[0] node, fn = self.get_underlying_method(node, arg, "__abs__") if fn is not None: return self.vm.call_function(node, fn, abstract.FunctionArgs(())) else: return node, self.vm.convert.create_new_unsolvable(node)
def test_call_wrong_argcount(self): self._vm.push_frame(frame_state.SimpleFrame()) node, result = self._is_instance.call( self._node, None, abstract.FunctionArgs((), self.new_dict(), None, None)) self.assertEqual(self._node, node) self.assertIsInstance(abstract.get_atomic_value(result), abstract.Unsolvable) self.assertRegexpMatches(str(self._vm.errorlog), "missing-parameter")
def test_call_wrong_keywords(self): self._vm.push_frame(frame_state.SimpleFrame()) x = self.new_var(abstract.Unknown(self._vm)) node, result = self._is_instance.call( self._node, None, abstract.FunctionArgs((x, x), self.new_dict(foo=x), None, None)) self.assertEqual(self._node, node) self.assertIsInstance(abstract.get_atomic_value(result), abstract.Unsolvable) self.assertRegexpMatches(str(self._vm.errorlog), r"foo.*isinstance.*\[wrong-keyword-args\]")
def test_call_wrong_argcount(self): self._vm.push_frame(FakeFrame()) node, result = self._is_instance.call( self._node, None, abstract.FunctionArgs((), self.new_dict(), None, None)) self.assertEquals(self._node, node) self.assertIsInstance(abstract.get_atomic_value(result), abstract.Unsolvable) self.assertRegexpMatches( str(self._vm.errorlog), r"isinstance.*expects 2.*got 0.*\[wrong-arg-count\]")
def testCallEmptyTypeParameterInstance(self): instance = abstract.Instance(self._vm.convert.list_type, self._vm) instance.initialize_type_parameter( self._node, abstract.T, self._vm.program.NewVariable()) t = abstract.TypeParameter(abstract.T, self._vm) t_instance = abstract.TypeParameterInstance(t, instance, self._vm) node, ret = t_instance.call(self._node, t_instance.to_binding(self._node), abstract.FunctionArgs(posargs=())) self.assertIs(node, self._node) retval, = ret.data self.assertIs(retval, self._vm.convert.empty)
def call(self, node, _, args): self._match_args(node, args) arg, default = self._get_args(args) node, fn = self.get_underlying_method(node, arg, "next") if fn is not None: node, ret = self.vm.call_function(node, fn, abstract.FunctionArgs(())) ret.PasteVariable(default) return node, ret else: # TODO(kramm): This needs a test case. return node, self.vm.convert.create_new_unsolvable(node)
def testCallTypeParameterInstance(self): instance = abstract.Instance(self._vm.convert.list_type, self._vm) instance.initialize_type_parameter( self._node, abstract.T, self._vm.convert.int_type.to_variable(self._vm.root_cfg_node)) t = abstract.TypeParameter(abstract.T, self._vm) t_instance = abstract.TypeParameterInstance(t, instance, self._vm) node, ret = t_instance.call(self._node, t_instance.to_binding(self._node), abstract.FunctionArgs(posargs=())) self.assertIs(node, self._node) retval, = ret.data self.assertListEqual(retval.cls.data, [self._vm.convert.int_type])
def make_class(self, node, f_locals): f_locals = abstract.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.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 = abstract.FunctionArgs(posargs=posargs) node, cls_var = self.namedtuple.call(node, None, args) cls_val = abstract.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.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.get_atomic_python_constant(fields, tuple) fields = [ abstract.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 _get_attribute_computed(self, node, cls, name, valself, compute_function): """Call compute_function (if defined) to compute an attribute.""" assert isinstance(cls, (abstract.Class, abstract.AMBIGUOUS_OR_EMPTY)), cls if (valself and not isinstance(valself.data, abstract.Module) and self._computable(name)): attr_var = self._lookup_from_mro(node, cls, compute_function, valself, skip=self.vm.convert.object_type) if attr_var and attr_var.bindings: name_var = abstract.AbstractOrConcreteValue( name, self.vm.convert.str_type, self.vm).to_variable(node) return self.vm.call_function( node, attr_var, abstract.FunctionArgs((name_var,))) return node, None
def testCallTypeParameterInstanceWithWrongArgs(self): instance = abstract.Instance(self._vm.convert.list_type, self._vm) instance.initialize_type_parameter( self._node, abstract.T, self._vm.convert.int_type.to_variable(self._vm.root_cfg_node)) t = abstract.TypeParameter(abstract.T, self._vm) t_instance = abstract.TypeParameterInstance(t, instance, self._vm) posargs = (self._vm.convert.create_new_unsolvable(self._node),) * 3 node, ret = t_instance.call(self._node, t_instance.to_binding(self._node), abstract.FunctionArgs(posargs=posargs)) self.assertIs(node, self._node) self.assertTrue(ret.bindings) error, = self._vm.errorlog self.assertEqual(error.name, "wrong-arg-count")
def call(self, node, _, args): posargs = args.posargs namedargs = self.vm.convert.value_to_constant(args.namedargs, dict) if namedargs and self.vm.python_version < (3, 6): errmsg = "Keyword syntax for NamedTuple is only supported in Python 3.6+" self.vm.errorlog.invalid_namedtuple_arg(self.vm.frames, err_msg=errmsg) if namedargs and len(posargs) == 1: namedargs = [ abstract.Tuple((self.vm.convert.build_string(node, k), v), self.vm).to_variable(node) for k, v in namedargs.items() ] namedargs = abstract.List(namedargs, self.vm).to_variable(node) posargs += (namedargs, ) args = abstract.FunctionArgs(posargs) elif namedargs: errmsg = ("Either list of fields or keywords can be provided to " "NamedTuple, not both") self.vm.errorlog.invalid_namedtuple_arg(self.vm.frames, err_msg=errmsg) return self.namedtuple.call(node, None, args)
def fget_slot(self, node, obj, objtype): return self.vm.call_function(node, self.fget, abstract.FunctionArgs((obj, )))
def _build_namedtuple(self, name, field_names, field_types, node): # Build an InterpreterClass representing the namedtuple. if field_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.K and abstract.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__ new_annots = {} new_lates = {} for (n, t) in moves.zip(field_names, field_types): # We don't support late annotations yet, but once we do, they'll show up # as LateAnnotation objects to be stored in new_lates. new_annots[n] = t # 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) new_annots["cls"] = abstract.ParameterizedClass( self.vm.convert.type_type, {abstract.T: cls_type_param}, self.vm) new_annots["return"] = cls_type_param members["__new__"] = abstract.SimpleFunction( name="__new__", param_names=("cls", ) + tuple(field_names), varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={}, annotations=new_annots, late_annotations=new_lates, vm=self.vm).to_variable(node) # __init__ members["__init__"] = abstract.SimpleFunction( name="__init__", param_names=("self", ), varargs_name="args", kwonly_params=(), kwargs_name="kwargs", defaults={}, annotations={}, late_annotations={}, vm=self.vm).to_variable(node) # _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.T: field_types_union}, self.vm) make = abstract.SimpleFunction( name="_make", param_names=("cls", "iterable", "new", "len"), varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={ "new": self.vm.convert.unsolvable.to_variable(node), "len": self.vm.convert.unsolvable.to_variable(node) }, annotations={ "cls": abstract.ParameterizedClass(self.vm.convert.type_type, {abstract.T: cls_type_param}, self.vm), "iterable": iterable_type, "new": self.vm.convert.unsolvable, "len": abstract.Callable( self.vm.convert.name_to_value("typing.Callable"), { 0: sized_cls, abstract.ARGS: sized_cls, abstract.RET: self.vm.convert.int_type }, self.vm), "return": cls_type_param }, late_annotations={}, vm=self.vm).to_variable(node) make_args = abstract.FunctionArgs(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"] = abstract.SimpleFunction( name="_replace", param_names=("self", ), varargs_name=None, kwonly_params=(), kwargs_name="kwds", defaults={}, annotations={ "self": cls_type_param, "kwds": field_types_union, "return": cls_type_param }, late_annotations={}, vm=self.vm).to_variable(node) # __getnewargs__ getnewargs_tuple_params = dict( tuple(enumerate(field_types)) + ((abstract.T, field_types_union), )) getnewargs_tuple = abstract.TupleClass(self.vm.convert.tuple_type, getnewargs_tuple_params, self.vm) members["__getnewargs__"] = abstract.SimpleFunction( name="__getnewargs__", param_names=("self", ), varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={}, annotations={ "return": getnewargs_tuple }, late_annotations={}, vm=self.vm).to_variable(node) # __getstate__ members["__getstate__"] = abstract.SimpleFunction( name="__getstate__", param_names=("self", ), varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={}, annotations={}, late_annotations={}, vm=self.vm).to_variable(node) # _asdict members["_asdict"] = abstract.SimpleFunction( name="_asdict", param_names=("self", ), varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={}, annotations={ "return": field_dict_cls }, late_annotations={}, vm=self.vm).to_variable(node) # Finally, make the class. abs_membs = abstract.Dict(self.vm) abs_membs.update(node, members) 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=abs_membs.to_variable(node), cls_var=None) # Now that the class has been made, we can complete the TypeParameter used # by __new__, _make and _replace. cls_type_param.bound = cls_var.data[0] return cls_var
def fdelete_slot(self, node, obj): return self.vm.call_function(node, self.fdel, abstract.FunctionArgs((obj, )))
def _call_pytd_function(self, f, args): b = f.to_variable(self._vm.root_cfg_node).bindings[0] return f.call(self._vm.root_cfg_node, b, abstract.FunctionArgs(posargs=args))
def fset_slot(self, node, obj, value): return self.vm.call_function(node, self.fset, abstract.FunctionArgs((obj, value)))