def _is_or_is_not_cmp(left, right, is_not=False): """Implementation of 'left is right' amd 'left is not right'.""" if (isinstance(left, abstract.PythonConstant) and isinstance(right, abstract.PythonConstant)): if left.cls != right.cls: return is_not return is_not ^ (left.pyval == right.pyval) elif (isinstance(left, abstract.Instance) and isinstance(right, abstract.Instance)): try: left_class = abstract.get_atomic_value(left.cls) right_class = abstract.get_atomic_value(right.cls) except abstract.ConversionError: # If multiple classes are possible we cannot be sure what happens. # Therefore returning None is fine. return None if left_class != right_class: # If those were the same they could be the same but we can't be sure from # comparing types. return is_not return None elif isinstance(left, abstract.Class) and isinstance(right, abstract.Class): # types are singletons. We use the name so that, e.g., two different # TupleClass instances compare as identical. return is_not ^ (left.full_name == right.full_name) else: return None
def test_getitem__concrete_index(self): t = abstract.Tuple((self._var, ), self._vm) index = self._vm.convert.constant_to_var(0) node, var = t.cls.data[0].getitem_slot(self._node, index) self.assertIs(node, self._node) self.assertIs(abstract.get_atomic_value(var), abstract.get_atomic_value(self._var))
def test_getitem__abstract_index(self): t = abstract.Tuple((self._var, ), self._vm) index = self._vm.convert.build_int(self._node) node, var = t.cls.data[0].getitem_slot(self._node, index) self.assertIs(node, self._node) self.assertIs(abstract.get_atomic_value(var), abstract.get_atomic_value(self._var))
def create_new_kwargs_value(self, arg_type): """Create a kwargs argument given its element type.""" params = { abstract.K: abstract.get_atomic_value(self.str_type), abstract.V: arg_type } return abstract.ParameterizedClass( abstract.get_atomic_value(self.dict_type), params, self.vm)
def _eval_expr_as_tuple(self, node, f_globals, f_locals, expr): if not expr: return () result = abstract.get_atomic_value( self._eval_expr(node, f_globals, f_locals, expr)) # If the result is a tuple, expand it. if (isinstance(result, abstract.PythonConstant) and isinstance(result.pyval, tuple)): return tuple(abstract.get_atomic_value(x) for x in result.pyval) else: return (result, )
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 setUp(self): super(IsInstanceTest, self).setUp() self._is_instance = abstract.IsInstance(self._vm) # Easier access to some primitive instances. self._bool = self._vm.convert.primitive_class_instances[bool] self._int = self._vm.convert.primitive_class_instances[int] self._str = self._vm.convert.primitive_class_instances[str] # Values that represent primitive classes. self._obj_class = abstract.get_atomic_value( self._vm.convert.primitive_classes[object]) self._int_class = abstract.get_atomic_value( self._vm.convert.primitive_classes[int]) self._str_class = abstract.get_atomic_value( self._vm.convert.primitive_classes[str])
def setUp(self): super(IsInstanceTest, self).setUp() self._is_instance = abstract.IsInstance(self._vm) # Easier access to some primitive instances. self._bool = self._vm.primitive_class_instances[bool] self._int = self._vm.primitive_class_instances[int] self._str = self._vm.primitive_class_instances[str] # Values that represent primitive classes. self._obj_class = abstract.get_atomic_value( self._vm.primitive_classes[object]) self._int_class = abstract.get_atomic_value( self._vm.primitive_classes[int]) self._str_class = abstract.get_atomic_value( self._vm.primitive_classes[str])
def testInstantiateInterpreterClass(self): cls = abstract.InterpreterClass("X", [], {}, None, self._vm) # When there is no current frame, create a new instance every time. v1 = abstract.get_atomic_value(cls.instantiate(self._node)) v2 = abstract.get_atomic_value(cls.instantiate(self._node)) self.assertIsNot(v1, v2) # Create one instance per opcode. fake_opcode = object() self._vm.push_frame(frame_state.SimpleFrame(fake_opcode)) v3 = abstract.get_atomic_value(cls.instantiate(self._node)) v4 = abstract.get_atomic_value(cls.instantiate(self._node)) self.assertIsNot(v1, v3) self.assertIsNot(v2, v3) self.assertIs(v3, v4)
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.get_atomic_python_constant(name_arg, str) except abstract.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.get_atomic_value(type_arg) except abstract.ConversionError: # We need the type arg to be an atomic value. If not, we just # silently return unsolvable. return node, self.vm.convert.create_new_unsolvable(node) value_arg_name = "val" constructor = abstract.SimpleFunction( name="__init__", param_names=("self", value_arg_name), varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={}, annotations={value_arg_name: type_value}, late_annotations={}, vm=self.vm, ).to_variable(node) members = abstract.Dict(self.vm) members.set_str_item(node, "__init__", constructor) return node, self.vm.make_class(node, name_arg, (type_arg,), members.to_variable(node), None)
def call(self, node, unused_func, args): """Adds a metaclass.""" self.match_args(node, args) meta = abstract.get_atomic_value(args.posargs[0], default=self.vm.convert.unsolvable) return node, AddMetaclassInstance(meta, self.vm, self.module_name).to_variable(node)
def _getargs(self, node, args): self._match_args(node, args) # Normally we would use typing.NamedTuple.__new__ to match args to # parameters, but we can't import typing. # TODO(tsudol): Replace with typing.NamedTuple.__new__. f = function.Signature.from_param_names("typing.NamedTuple", ["typename", "fields"]) callargs = { arg_name: arg_var for arg_name, arg_var, _ in f.iter_args(args) } # typing.NamedTuple doesn't support rename or verbose name_var = callargs["typename"] fields_var = callargs["fields"] fields = abstract.get_atomic_python_constant(fields_var) # The fields is a list of tuples, so we need to deeply unwrap them. fields = [abstract.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 (name, typ) in fields: names.append(abstract.get_atomic_python_constant(name)) types.append(abstract.get_atomic_value(typ)) return name_var, names, types
def _is_instance(self, obj, class_spec): """Check if the object matches a class specification. Args: obj: An AtomicAbstractValue, generally the left hand side of an isinstance() call. class_spec: An AtomicAbstractValue, generally the right hand side of an isinstance() call. Returns: True if the object is derived from a class in the class_spec, False if it is not, and None if it is ambiguous whether obj matches class_spec. """ if isinstance(obj, abstract.AMBIGUOUS_OR_EMPTY): return None # Assume a single binding for the object's class variable. If this isn't # the case, treat the call as ambiguous. cls_var = obj.get_class() if cls_var is None: return None try: obj_class = abstract.get_atomic_value(cls_var) except abstract.ConversionError: return None return _check_against_mro(self.vm, obj_class, class_spec)
def _getargs(self, node, args): self._match_args(node, args) # Normally we would use typing.NamedTuple.__new__ to match args to # parameters, but we can't import typing. # TODO(tsudol): Replace with typing.NamedTuple.__new__. f = function.Signature.from_param_names("typing.NamedTuple", ["typename", "fields"]) callargs = { arg_name: arg_var for arg_name, arg_var, _ in f.iter_args(args) } # typing.NamedTuple doesn't support rename or verbose name_var = callargs["typename"] fields_var = callargs["fields"] fields = abstract.get_atomic_python_constant(fields_var) # The fields is a list of tuples, so we need to deeply unwrap them. fields = [abstract.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 = abstract.BadParam(name="fields", expected=self._fields_type) raise abstract.WrongArgTypes(sig.signature, args, self.vm, bad_param) name, typ = field names.append(abstract.get_atomic_python_constant(name)) types.append(abstract.get_atomic_value(typ)) return name_var, names, types
def test_callable_with_args(self): ast = self._load_ast( "a", """ from typing import Callable x = ... # type: Callable[[int, bool], str] """) x = ast.Lookup("a.x").type cls = self._vm.convert.constant_to_value(x, {}, self._vm.root_cfg_node) instance = self._vm.convert.constant_to_value(abstract.AsInstance(x), {}, self._vm.root_cfg_node) self.assertIsInstance(cls, abstract.Callable) self.assertItemsEqual( cls.type_parameters.items(), [(0, self._vm.convert.int_type), (1, self._vm.convert.primitive_classes[bool]), (abstract.ARGS, abstract.Union([cls.type_parameters[0], cls.type_parameters[1]], self._vm)), (abstract.RET, self._vm.convert.str_type)]) self.assertIsInstance(instance, abstract.Instance) self.assertEqual(abstract.get_atomic_value(instance.cls), cls) self.assertItemsEqual([ (name, set(var.data)) for name, var in instance.type_parameters.items() ], [(abstract.ARGS, { self._vm.convert.primitive_class_instances[int], self._vm.convert.primitive_class_instances[bool] }), (abstract.RET, {self._vm.convert.primitive_class_instances[str]})])
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.get_atomic_python_constant(fields_var) # The fields is a list of tuples, so we need to deeply unwrap them. fields = [abstract.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 = abstract.BadParam(name="fields", expected=self._fields_type) raise abstract.WrongArgTypes(sig.signature, args, self.vm, bad_param) name, typ = field names.append(abstract.get_atomic_python_constant(name)) types.append(abstract.get_atomic_value(typ)) return name_var, names, types
def apply_type_comment(self, state, op, name, value): """If there is a type comment for the op, return its value.""" assert op is self.vm.frame.current_opcode if op.code.co_filename != self.vm.filename: return value if not op.type_comment: return value comment = op.type_comment try: var = self._eval_expr(state.node, self.vm.frame.f_globals, self.vm.frame.f_locals, comment) except EvaluationError as e: self.vm.errorlog.invalid_type_comment(self.vm.frames, comment, details=e.message) value = self.vm.convert.create_new_unsolvable(state.node) else: try: typ = abstract.get_atomic_value(var) except abstract.ConversionError: self.vm.errorlog.invalid_type_comment( self.vm.frames, comment, details="Must be constant.") value = self.vm.convert.create_new_unsolvable(state.node) else: if self.get_type_parameters(typ): self.vm.errorlog.not_supported_yet( self.vm.frames, "using type parameter in type comment") try: value = self.init_annotation(typ, name, self.vm.frames, state.node) except self.LateAnnotationError: value = LateAnnotation(typ, name, self.vm.simple_stack()) return value
def call(self, node, unused_func, args): """Creates an anonymous class to act as a metaclass.""" self.match_args(node, args) meta = abstract.get_atomic_value(args.posargs[0], default=self.vm.convert.unsolvable) bases = args.posargs[1:] result = WithMetaclassInstance(self.vm, meta, bases).to_variable(node) return node, result
def test_call_wrong_argcount(self): self._vm.push_frame(FakeFrame()) node, result = self._is_instance.call(self._node, None, (), self.new_dict()) self.assertEquals(self._node, node) self.assertIsInstance(abstract.get_atomic_value(result), abstract.Unsolvable) self.assertRegexpMatches( str(self._vm.errorlog), r"isinstance .* 0 args .* expected 2.*\[wrong-arg-count\]")
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(FakeFrame()) x = self.new_var("x", abstract.Unknown(self._vm)) node, result = self._is_instance.call(self._node, None, (x, x), self.new_dict(foo=x)) self.assertEquals(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, (), self.new_dict()) self.assertEquals(self._node, node) self.assertIsInstance(abstract.get_atomic_value(result), abstract.Unsolvable) self.assertRegexpMatches( str(self._vm.errorlog), r"isinstance .* 0 args .* expected 2.*\[wrong-arg-count\]")
def test_call_wrong_keywords(self): self._vm.push_frame(FakeFrame()) x = self.new_var("x", abstract.Unknown(self._vm)) node, result = self._is_instance.call( self._node, None, (x, x), self.new_dict(foo=x)) self.assertEquals(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_generic_with_any_param(self): ast = self._load_ast( "a", """ from typing import Dict x = ... # type: Dict[str] """) val = self._vm.convert.constant_to_value("x", ast.Lookup("a.x").type, {}, self._vm.root_cfg_node) self.assertIs(val.type_parameters["K"], abstract.get_atomic_value(self._vm.convert.str_type)) self.assertIs(val.type_parameters["V"], self._vm.convert.unsolvable)
def test_callable_no_args(self): ast = self._load_ast("a", """ from typing import Callable x = ... # type: Callable[[], ...] """) x = ast.Lookup("a.x").type cls = self._vm.convert.constant_to_value(x, {}, self._vm.root_cfg_node) instance = self._vm.convert.constant_to_value( abstract.AsInstance(x), {}, self._vm.root_cfg_node) self.assertIsInstance(cls.type_parameters[abstract.ARGS], abstract.Empty) self.assertEqual( abstract.get_atomic_value(instance.type_parameters[abstract.ARGS]), self._vm.convert.empty)
def test_signature_annotations(self): # def f(self: Any, *args: Any) self_param = pytd.Parameter("self", pytd.AnythingType(), False, False, None) args_param = pytd.Parameter("args", pytd.AnythingType(), False, True, None) sig = function.Signature.from_pytd( self._vm, "f", pytd.Signature((self_param, ), args_param, None, pytd.AnythingType(), (), ())) self.assertIs(sig.annotations["self"], self._vm.convert.unsolvable) args_type = sig.annotations["args"] # Should be Tuple[Any] self.assertIsInstance(args_type, abstract.ParameterizedClass) self.assertIs(args_type.base_cls, abstract.get_atomic_value(self._vm.convert.tuple_type)) self.assertDictEqual(args_type.type_parameters, {abstract.T: self._vm.convert.unsolvable}) self.assertIs(sig.drop_first_parameter().annotations["args"], args_type)
def test_plain_callable(self): ast = self._load_ast("a", """ from typing import Callable x = ... # type: Callable[..., int] """) x = ast.Lookup("a.x").type cls = self._vm.convert.constant_to_value(x, {}, self._vm.root_cfg_node) instance = self._vm.convert.constant_to_value( abstract.AsInstance(x), {}, self._vm.root_cfg_node) self.assertIsInstance(cls, abstract.ParameterizedClass) self.assertItemsEqual(cls.type_parameters.items(), [(abstract.ARGS, self._vm.convert.unsolvable), (abstract.RET, self._vm.convert.int_type)]) self.assertIsInstance(instance, abstract.Instance) self.assertEqual(abstract.get_atomic_value(instance.cls), cls.base_cls) self.assertItemsEqual( [(name, var.data) for name, var in instance.type_parameters.items()], [(abstract.ARGS, [self._vm.convert.unsolvable]), (abstract.RET, [self._vm.convert.primitive_class_instances[int]])])
def __init__(self, name, vm): base = abstract.get_atomic_value(vm.convert.function_type) super(Callable, self).__init__(name, vm, base)
def setUp(self): self.vm = vm.VirtualMachine(errors.ErrorLog(), config.Options([""])) self.type_type = abstract.get_atomic_value(self.vm.convert.type_type)
def _static_method_to_def(self, node, v, name, kind): """Convert a staticmethod to a PyTD definition.""" # This line may raise abstract.ConversionError func = abstract.get_atomic_value(v.func, abstract.Function) return self.value_to_pytd_def(node, func, name).Replace(kind=kind)
def create_new_varargs_value(self, arg_type): """Create a varargs argument given its element type.""" params = {abstract.T: arg_type} return abstract.ParameterizedClass( abstract.get_atomic_value(self.tuple_type), params, self.vm)