def test_is_subtype(self) -> None: ast_nodes = builder.extract_node(""" class int_subclass(int): pass class A(object): pass #@ class B(A): pass #@ class C(A): pass #@ int_subclass() #@ """) assert isinstance(ast_nodes, list) cls_a = ast_nodes[0] cls_b = ast_nodes[1] cls_c = ast_nodes[2] int_subclass = ast_nodes[3] int_subclass = helpers.object_type(next(int_subclass.infer())) base_int = self._extract("int") self.assertTrue(helpers.is_subtype(int_subclass, base_int)) self.assertTrue(helpers.is_supertype(base_int, int_subclass)) self.assertTrue(helpers.is_supertype(cls_a, cls_b)) self.assertTrue(helpers.is_supertype(cls_a, cls_c)) self.assertTrue(helpers.is_subtype(cls_b, cls_a)) self.assertTrue(helpers.is_subtype(cls_c, cls_a)) self.assertFalse(helpers.is_subtype(cls_a, cls_b)) self.assertFalse(helpers.is_subtype(cls_a, cls_b))
def test_is_subtype_supertype_unrelated_classes(self) -> None: cls_a, cls_b = builder.extract_node(""" class A(object): pass #@ class B(object): pass #@ """) self.assertFalse(helpers.is_subtype(cls_a, cls_b)) self.assertFalse(helpers.is_subtype(cls_b, cls_a)) self.assertFalse(helpers.is_supertype(cls_a, cls_b)) self.assertFalse(helpers.is_supertype(cls_b, cls_a))
def test_is_subtype_supertype_unrelated_classes(self): cls_a, cls_b = test_utils.extract_node(''' class A(object): pass #@ class B(object): pass #@ ''') self.assertFalse(helpers.is_subtype(cls_a, cls_b)) self.assertFalse(helpers.is_subtype(cls_b, cls_a)) self.assertFalse(helpers.is_supertype(cls_a, cls_b)) self.assertFalse(helpers.is_supertype(cls_b, cls_a))
def test_is_subtype_supertype_unrelated_classes(self): cls_a, cls_b = builder.extract_node(''' class A(object): pass #@ class B(object): pass #@ ''') self.assertFalse(helpers.is_subtype(cls_a, cls_b)) self.assertFalse(helpers.is_subtype(cls_b, cls_a)) self.assertFalse(helpers.is_supertype(cls_a, cls_b)) self.assertFalse(helpers.is_supertype(cls_b, cls_a))
def test_is_subtype_supertype_unknown_bases(self): cls_a, cls_b = builder.extract_node(''' from unknown import Unknown class A(Unknown): pass #@ class B(A): pass #@ ''') with self.assertRaises(exceptions._NonDeducibleTypeHierarchy): helpers.is_subtype(cls_a, cls_b) with self.assertRaises(exceptions._NonDeducibleTypeHierarchy): helpers.is_supertype(cls_a, cls_b)
def test_is_subtype_supertype_unknown_bases(self) -> None: cls_a, cls_b = builder.extract_node(""" from unknown import Unknown class A(Unknown): pass #@ class B(A): pass #@ """) with self.assertRaises(_NonDeducibleTypeHierarchy): helpers.is_subtype(cls_a, cls_b) with self.assertRaises(_NonDeducibleTypeHierarchy): helpers.is_supertype(cls_a, cls_b)
def test_is_subtype_supertype_old_style_classes(self): cls_a, cls_b = builder.extract_node(''' class A: #@ pass class B(A): #@ pass ''') self.assertFalse(helpers.is_subtype(cls_a, cls_b)) self.assertFalse(helpers.is_subtype(cls_b, cls_a)) self.assertFalse(helpers.is_supertype(cls_a, cls_b)) self.assertFalse(helpers.is_supertype(cls_b, cls_a))
def test_is_subtype_supertype_mro_error(self): cls_e, cls_f = test_utils.extract_node(''' class A(object): pass class B(A): pass class C(A): pass class D(B, C): pass class E(C, B): pass #@ class F(D, E): pass #@ ''') self.assertFalse(helpers.is_subtype(cls_e, cls_f)) self.assertEqual(helpers.is_subtype(cls_f, cls_e), util.Uninferable) self.assertEqual(helpers.is_supertype(cls_e, cls_f), util.Uninferable) self.assertFalse(helpers.is_supertype(cls_f, cls_e))
def test_is_subtype_supertype_mro_error(self) -> None: cls_e, cls_f = builder.extract_node(""" class A(object): pass class B(A): pass class C(A): pass class D(B, C): pass class E(C, B): pass #@ class F(D, E): pass #@ """) self.assertFalse(helpers.is_subtype(cls_e, cls_f)) self.assertFalse(helpers.is_subtype(cls_e, cls_f)) with self.assertRaises(_NonDeducibleTypeHierarchy): helpers.is_subtype(cls_f, cls_e) self.assertFalse(helpers.is_supertype(cls_f, cls_e))
def test_is_subtype_supertype_mro_error(self): cls_e, cls_f = builder.extract_node(''' class A(object): pass class B(A): pass class C(A): pass class D(B, C): pass class E(C, B): pass #@ class F(D, E): pass #@ ''') self.assertFalse(helpers.is_subtype(cls_e, cls_f)) self.assertFalse(helpers.is_subtype(cls_e, cls_f)) with self.assertRaises(exceptions._NonDeducibleTypeHierarchy): helpers.is_subtype(cls_f, cls_e) self.assertFalse(helpers.is_supertype(cls_f, cls_e))
def infer_attribute(self, context=None): """infer an Attribute node by using getattr on the associated object""" for owner in self.expr.infer(context): if owner is util.Uninferable: yield owner continue if context and context.boundnode: # This handles the situation where the attribute is accessed through a subclass # of a base class and the attribute is defined at the base class's level, # by taking in consideration a redefinition in the subclass. if (isinstance(owner, bases.Instance) and isinstance(context.boundnode, bases.Instance)): try: if helpers.is_subtype(helpers.object_type(context.boundnode), helpers.object_type(owner)): owner = context.boundnode except exceptions._NonDeducibleTypeHierarchy: # Can't determine anything useful. pass try: context.boundnode = owner yield from owner.igetattr(self.attrname, context) context.boundnode = None except (exceptions.AttributeInferenceError, exceptions.InferenceError): context.boundnode = None except AttributeError: # XXX method / function context.boundnode = None # Explicit StopIteration to return error information, see comment # in raise_if_nothing_inferred. return dict(node=self, context=context)
def _get_binop_flow(left, left_type, binary_opnode, right, right_type, context, reverse_context): """Get the flow for binary operations. The rules are a bit messy: * if left and right have the same type, then only one method will be called, left.__op__(right) * if left and right are unrelated typewise, then first left.__op__(right) is tried and if this does not exist or returns NotImplemented, then right.__rop__(left) is tried. * if left is a subtype of right, then only left.__op__(right) is tried. * if left is a supertype of right, then right.__rop__(left) is first tried and then left.__op__(right) """ op = binary_opnode.op if _same_type(left_type, right_type): methods = [_bin_op(left, binary_opnode, op, right, context)] elif helpers.is_subtype(left_type, right_type): methods = [_bin_op(left, binary_opnode, op, right, context)] elif helpers.is_supertype(left_type, right_type): methods = [_bin_op(right, binary_opnode, op, left, reverse_context, reverse=True), _bin_op(left, binary_opnode, op, right, context)] else: methods = [_bin_op(left, binary_opnode, op, right, context), _bin_op(right, binary_opnode, op, left, reverse_context, reverse=True)] return methods
def _get_aug_flow(left, left_type, aug_opnode, right, right_type, context, reverse_context): """Get the flow for augmented binary operations. The rules are a bit messy: * if left and right have the same type, then left.__augop__(right) is first tried and then left.__op__(right). * if left and right are unrelated typewise, then left.__augop__(right) is tried, then left.__op__(right) is tried and then right.__rop__(left) is tried. * if left is a subtype of right, then left.__augop__(right) is tried and then left.__op__(right). * if left is a supertype of right, then left.__augop__(right) is tried, then right.__rop__(left) and then left.__op__(right) """ bin_op = aug_opnode.op.strip("=") aug_op = aug_opnode.op if _same_type(left_type, right_type): methods = [_aug_op(left, aug_opnode, aug_op, right, context), _bin_op(left, aug_opnode, bin_op, right, context)] elif helpers.is_subtype(left_type, right_type): methods = [_aug_op(left, aug_opnode, aug_op, right, context), _bin_op(left, aug_opnode, bin_op, right, context)] elif helpers.is_supertype(left_type, right_type): methods = [_aug_op(left, aug_opnode, aug_op, right, context), _bin_op(right, aug_opnode, bin_op, left, reverse_context, reverse=True), _bin_op(left, aug_opnode, bin_op, right, context)] else: methods = [_aug_op(left, aug_opnode, aug_op, right, context), _bin_op(left, aug_opnode, bin_op, right, context), _bin_op(right, aug_opnode, bin_op, left, reverse_context, reverse=True)] return methods
def infer_attribute(self, context=None): """infer an Attribute node by using getattr on the associated object""" for owner in self.expr.infer(context): if owner is util.Uninferable: yield owner continue if context and context.boundnode: # This handles the situation where the attribute is accessed through a subclass # of a base class and the attribute is defined at the base class's level, # by taking in consideration a redefinition in the subclass. if (isinstance(owner, bases.Instance) and isinstance(context.boundnode, bases.Instance)): try: if helpers.is_subtype(helpers.object_type(context.boundnode), helpers.object_type(owner)): owner = context.boundnode except exceptions._NonDeducibleTypeHierarchy: # Can't determine anything useful. pass try: context.boundnode = owner for obj in owner.igetattr(self.attrname, context): yield obj context.boundnode = None except (exceptions.AttributeInferenceError, exceptions.InferenceError): context.boundnode = None except AttributeError: # XXX method / function context.boundnode = None # Explicit StopIteration to return error information, see comment # in raise_if_nothing_inferred. raise StopIteration(dict(node=self, context=context))
def _get_binop_flow( left, left_type, binary_opnode, right, right_type, context, reverse_context ): """Get the flow for binary operations. The rules are a bit messy: * if left and right have the same type, then only one method will be called, left.__op__(right) * if left and right are unrelated typewise, then first left.__op__(right) is tried and if this does not exist or returns NotImplemented, then right.__rop__(left) is tried. * if left is a subtype of right, then only left.__op__(right) is tried. * if left is a supertype of right, then right.__rop__(left) is first tried and then left.__op__(right) """ op = binary_opnode.op if _same_type(left_type, right_type): methods = [_bin_op(left, binary_opnode, op, right, context)] elif helpers.is_subtype(left_type, right_type): methods = [_bin_op(left, binary_opnode, op, right, context)] elif helpers.is_supertype(left_type, right_type): methods = [ _bin_op(right, binary_opnode, op, left, reverse_context, reverse=True), _bin_op(left, binary_opnode, op, right, context), ] else: methods = [ _bin_op(left, binary_opnode, op, right, context), _bin_op(right, binary_opnode, op, left, reverse_context, reverse=True), ] return methods
def test_is_subtype_supertype_classes_metaclasses(self): cls_a = builder.extract_node(''' class A(type): #@ pass ''') builtin_type = self._extract('type') self.assertTrue(helpers.is_supertype(builtin_type, cls_a)) self.assertTrue(helpers.is_subtype(cls_a, builtin_type))
def test_is_subtype_supertype_classes_no_type_ancestor(self): cls_a = builder.extract_node(''' class A(object): #@ pass ''') builtin_type = self._extract('type') self.assertFalse(helpers.is_supertype(builtin_type, cls_a)) self.assertFalse(helpers.is_subtype(cls_a, builtin_type))
def test_is_subtype_supertype_unknown_bases(self): cls_a, cls_b = test_utils.extract_node(''' from unknown import Unknown class A(Unknown): pass #@ class B(A): pass #@ ''') self.assertTrue(helpers.is_subtype(cls_b, cls_a)) self.assertTrue(helpers.is_supertype(cls_a, cls_b))
def test_is_subtype_supertype_classes_no_type_ancestor(self) -> None: cls_a = builder.extract_node(""" class A(object): #@ pass """) builtin_type = self._extract("type") self.assertFalse(helpers.is_supertype(builtin_type, cls_a)) self.assertFalse(helpers.is_subtype(cls_a, builtin_type))
def test_is_subtype_supertype_classes_metaclasses(self) -> None: cls_a = builder.extract_node(""" class A(type): #@ pass """) builtin_type = self._extract("type") self.assertTrue(helpers.is_supertype(builtin_type, cls_a)) self.assertTrue(helpers.is_subtype(cls_a, builtin_type))
def test_is_subtype(self): ast_nodes = builder.extract_node(''' class int_subclass(int): pass class A(object): pass #@ class B(A): pass #@ class C(A): pass #@ int_subclass() #@ ''') cls_a = ast_nodes[0] cls_b = ast_nodes[1] cls_c = ast_nodes[2] int_subclass = ast_nodes[3] int_subclass = helpers.object_type(next(int_subclass.infer())) base_int = self._extract('int') self.assertTrue(helpers.is_subtype(int_subclass, base_int)) self.assertTrue(helpers.is_supertype(base_int, int_subclass)) self.assertTrue(helpers.is_supertype(cls_a, cls_b)) self.assertTrue(helpers.is_supertype(cls_a, cls_c)) self.assertTrue(helpers.is_subtype(cls_b, cls_a)) self.assertTrue(helpers.is_subtype(cls_c, cls_a)) self.assertFalse(helpers.is_subtype(cls_a, cls_b)) self.assertFalse(helpers.is_subtype(cls_a, cls_b))
def query_attribute(self, context=None): """query an Attribute node by using getattr on the associated object""" res = [] for owner in self.expr.query(context): if owner is util.Uninferable: assert False if owner is util.Unqueryable: continue if owner.query_end: res.extend([owner, util.Unqueryable]) continue if context and context.boundnode: # This handles the situation where the attribute is accessed through a subclass # of a base class and the attribute is defined at the base class's level, # by taking in consideration a redefinition in the subclass. if isinstance(owner, bases.Instance) and isinstance( context.boundnode, bases.Instance): try: if helpers.is_subtype( helpers.object_type(context.boundnode), helpers.object_type(owner), ): owner = context.boundnode except exceptions._NonDeducibleTypeHierarchy: # Can't determine anything useful. pass elif not context: context = contextmod.InferenceContext() try: context.boundnode = owner res.extend(owner.query_attr(self.attrname, context)) except ( exceptions.AttributeInferenceError, exceptions.InferenceError, AttributeError, ): import traceback traceback.print_exc() pass finally: context.boundnode = None if len(res) == 0: return [util.Unqueryable] return res
def is_subclass_of(child: astroid.ClassDef, parent: astroid.ClassDef) -> bool: """ Check if first node is a subclass of second node. :param child: Node to check for subclass. :param parent: Node to check for superclass. :returns: True if child is derived from parent. False otherwise. """ if not all(isinstance(node, astroid.ClassDef) for node in (child, parent)): return False for ancestor in child.ancestors(): try: if helpers.is_subtype(ancestor, parent): return True except _NonDeducibleTypeHierarchy: continue return False
def infer_attribute(self, context=None): """infer an Attribute node by using getattr on the associated object""" for owner in self.expr.infer(context): if owner is util.Uninferable: yield owner continue if context and context.boundnode: # This handles the situation where the attribute is accessed through a subclass # of a base class and the attribute is defined at the base class's level, # by taking in consideration a redefinition in the subclass. if isinstance(owner, bases.Instance) and isinstance( context.boundnode, bases.Instance ): try: if helpers.is_subtype( helpers.object_type(context.boundnode), helpers.object_type(owner), ): owner = context.boundnode except _NonDeducibleTypeHierarchy: # Can't determine anything useful. pass elif not context: context = contextmod.InferenceContext() old_boundnode = context.boundnode try: context.boundnode = owner yield from owner.igetattr(self.attrname, context) except ( AttributeInferenceError, InferenceError, AttributeError, ): pass finally: context.boundnode = old_boundnode return dict(node=self, context=context)