def test_call_with_kwonly_args(self): f = self._make_func( param_names=("test",), kwonly_params=("a", "b"), annotations={ "test": self._ctx.convert.str_type, "a": self._ctx.convert.str_type, "b": self._ctx.convert.str_type }) kwargs = abstract.Dict(self._ctx) kwargs.update( self._ctx.root_node, { "a": self._ctx.convert.build_string(self._ctx.root_node, "2"), "b": self._ctx.convert.build_string(self._ctx.root_node, "3") }) kwargs = kwargs.to_variable(self._ctx.root_node) args = function.Args( posargs=(self._ctx.convert.build_string(self._ctx.root_node, "1"),), namedargs=abstract.Dict(self._ctx), starstarargs=kwargs) f.call(self._ctx.root_node, f, args) kwargs = abstract.Dict(self._ctx) kwargs.update( self._ctx.root_node, {"b": self._ctx.convert.build_string(self._ctx.root_node, "3")}) kwargs = kwargs.to_variable(self._ctx.root_node) args = function.Args( posargs=(self._ctx.convert.build_string(self._ctx.root_node, "1"), self._ctx.convert.build_int(self._ctx.root_node)), namedargs=abstract.Dict(self._ctx), starstarargs=kwargs) self.assertRaises(function.MissingParameter, f.call, self._ctx.root_node, f, args)
def test_call_with_all_args(self): f = self._make_func( param_names=("a", "b", "c"), varargs_name="arg", kwargs_name="kwarg", defaults=(self._ctx.convert.build_int(self._ctx.root_node),), annotations={ "a": self._ctx.convert.str_type, "b": self._ctx.convert.int_type, "c": self._ctx.convert.int_type, "arg": self._ctx.convert.primitive_classes[float], "kwarg": self._ctx.convert.primitive_classes[bool] }) posargs = (self._ctx.convert.build_string(self._ctx.root_node, "1"), self._ctx.convert.build_int(self._ctx.root_node)) float_inst = self._ctx.convert.primitive_class_instances[float] stararg = self._ctx.convert.build_tuple( self._ctx.root_node, (float_inst.to_variable(self._ctx.root_node),)) namedargs = abstract.Dict(self._ctx) kwarg = abstract.Dict(self._ctx) kwarg.update( self._ctx.root_node, { "x": self._ctx.convert.build_bool(self._ctx.root_node), "y": self._ctx.convert.build_bool(self._ctx.root_node) }) kwarg = kwarg.to_variable(self._ctx.root_node) args = function.Args(posargs, namedargs, stararg, kwarg) f.call(self._ctx.root_node, f, args)
def test_call_with_bad_kwargs(self): f = self._make_func( kwargs_name="kwarg", annotations={"kwarg": self._ctx.convert.str_type}) kwargs = abstract.Dict(self._ctx) kwargs.update(self._ctx.root_node, {"_1": self._ctx.convert.build_int(self._ctx.root_node)}) kwargs = kwargs.to_variable(self._ctx.root_node) args = function.Args( posargs=(), namedargs=abstract.Dict(self._ctx), starstarargs=kwargs) self.assertRaises(function.WrongArgTypes, f.call, self._ctx.root_node, f, args)
def test_call_with_kwargs(self): f = self._make_func( kwargs_name="kwarg", annotations={"kwarg": self._ctx.convert.str_type}) kwargs = abstract.Dict(self._ctx) kwargs.update( self._ctx.root_node, { "_1": self._ctx.convert.build_string(self._ctx.root_node, "1"), "_2": self._ctx.convert.build_string(self._ctx.root_node, "2") }) kwargs = kwargs.to_variable(self._ctx.root_node) args = function.Args( posargs=(), namedargs=abstract.Dict(self._ctx), starstarargs=kwargs) f.call(self._ctx.root_node, f, args)
def call(self, node, func, args): args = args.simplify(node, self.ctx) 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.ctx.convert.constant_to_var( f"_NewType_Internal_Class_Name_{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.ctx.new_unsolvable(node) value_arg_name = "val" constructor = overlay_utils.make_method( self.ctx, node, name="__init__", params=[Param(value_arg_name, type_value)]) members = abstract.Dict(self.ctx) members.set_str_item(node, "__init__", constructor) return self.ctx.make_class(node, name_arg, (type_arg, ), members.to_variable(node), None)
def test_compatible_with__after_ambiguous_update(self): ambiguous_dict = abstract.Dict(self._ctx) ambiguous_dict.merge_instance_type_parameter( self._node, abstract_utils.K, self._ctx.new_unsolvable(self._node)) ambiguous_dict.could_contain_anything = True self._d.update(self._node, ambiguous_dict) self.assertAmbiguous(self._d)
def build_map_unpack(state, arg_list, ctx): """Merge a list of kw dicts into a single dict.""" args = abstract.Dict(ctx) for arg in arg_list: for data in arg.data: args.update(state.node, data) args = args.to_variable(state.node) return args
def call(self, node, funcb, args): if self.ctx.options.build_dict_literals_from_kwargs: build_literal = not args.has_non_namedargs() else: build_literal = args.is_empty() if build_literal: # special-case a dict constructor with explicit k=v args d = abstract.Dict(self.ctx) for (k, v) in args.namedargs.items(): d.set_str_item(node, k, v) return node, d.to_variable(node) else: return super().call(node, funcb, args)
def test_compatible_with__after_unambiguous_update(self): unambiguous_dict = abstract.Dict(self._ctx) unambiguous_dict.set_str_item(self._node, "a", self._ctx.new_unsolvable(self._node)) self._d.update(self._node, unambiguous_dict) self.assertTruthy(self._d)
def test_compatible_with__after_empty_update(self): empty_dict = abstract.Dict(self._ctx) self._d.update(self._node, empty_dict) self.assertFalsy(self._d)
def setUp(self): super().setUp() self._d = abstract.Dict(self._ctx) self._var = self._program.NewVariable() self._var.AddBinding(abstract.Unknown(self._ctx), [], self._node)
def new_dict(self, **kwargs): """Create a Dict from keywords mapping names to Variable objects.""" d = abstract.Dict(self._ctx) for name, var in kwargs.items(): d.set_str_item(self._node, name, var) return d
def build_map(self, node): """Create an empty VM dict.""" return abstract.Dict(self.ctx).to_variable(node)
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