Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pycqa/astroid
1 result
Show changes
Commits on Source (9)
Loading
Loading
@@ -6,6 +6,10 @@ Change log for the astroid package (used to be astng)
 
Part of PyCQA/pylint#811
 
* Add brain tip for `issubclass` builtin
Close #101.
* Fix submodule imports from six
 
Close PYCQA/pylint#1640
Loading
Loading
@@ -61,6 +65,10 @@ Change log for the astroid package (used to be astng)
 
Close #1699
 
* Implement inference for len builtin
Close #112
 
2017-12-15 -- 1.6.0
 
Loading
Loading
Loading
Loading
@@ -516,6 +516,48 @@ def _infer_object__new__decorator_check(node):
return False
 
 
def infer_issubclass(callnode, context=None):
"""Infer issubclass() calls
:param nodes.Call callnode: a `issubclass` call
:param InferenceContext: the context for the inference
:rtype nodes.Const: Boolean Const value of the `issubclass` call
:raises UseInferenceDefault: If the node cannot be inferred
"""
call = arguments.CallSite.from_call(callnode)
if call.keyword_arguments:
# issubclass doesn't support keyword arguments
raise UseInferenceDefault("TypeError: issubclass() takes no keyword arguments")
if len(call.positional_arguments) != 2:
raise UseInferenceDefault(
"Expected two arguments, got {count}"
.format(count=len(call.positional_arguments)))
# The left hand argument is the obj to be checked
obj_node, class_or_tuple_node = call.positional_arguments
try:
obj_type = next(obj_node.infer(context=context))
except InferenceError as exc:
raise UseInferenceDefault from exc
if not isinstance(obj_type, nodes.ClassDef):
raise UseInferenceDefault("TypeError: arg 1 must be class")
# The right hand argument is the class(es) that the given
# object is to be checked against.
try:
class_container = _class_or_tuple_to_container(
class_or_tuple_node, context=context)
except InferenceError as exc:
raise UseInferenceDefault from exc
try:
issubclass_bool = helpers.object_issubclass(obj_type, class_container, context)
except AstroidTypeError as exc:
raise UseInferenceDefault("TypeError: " + str(exc)) from exc
except MroError as exc:
raise UseInferenceDefault from exc
return nodes.Const(issubclass_bool)
def infer_isinstance(callnode, context=None):
"""Infer isinstance calls
 
Loading
Loading
@@ -576,6 +618,29 @@ def _class_or_tuple_to_container(node, context=None):
return class_container
 
 
def infer_len(node, context=None):
"""Infer length calls
:param nodes.Call node: len call to infer
:param context.InferenceContext: node context
:rtype nodes.Const:
"""
call = arguments.CallSite.from_call(node)
if call.keyword_arguments:
raise UseInferenceDefault(
"TypeError: len() must take no keyword arguments")
if len(call.positional_arguments) != 1:
raise UseInferenceDefault(
"TypeError: len() must take exactly one argument "
"({len}) given".format(len=len(call.positional_arguments)))
[argument_node] = call.positional_arguments
try:
return nodes.Const(helpers.object_len(argument_node))
except (AstroidTypeError, InferenceError) as exc:
raise UseInferenceDefault(str(exc)) from exc
# Builtins inference
register_builtin_transform(infer_bool, 'bool')
register_builtin_transform(infer_super, 'super')
Loading
Loading
@@ -590,6 +655,8 @@ register_builtin_transform(infer_frozenset, 'frozenset')
register_builtin_transform(infer_type, 'type')
register_builtin_transform(infer_slice, 'slice')
register_builtin_transform(infer_isinstance, 'isinstance')
register_builtin_transform(infer_issubclass, 'issubclass')
register_builtin_transform(infer_len, 'len')
 
# Infer object.__new__ calls
MANAGER.register_transform(
Loading
Loading
Loading
Loading
@@ -83,39 +83,62 @@ def object_type(node, context=None):
return list(types)[0]
 
 
def object_isinstance(node, class_or_seq, context=None):
"""Check if a node 'isinstance' any node in class_or_seq
:param node: A given node
:param class_or_seq: Union[Nodes.NodeNG], Sequence[nodes.NodeNG]]
:rtype: bool
:raises AstroidTypeError: if the given ``classes_or_seq`` are not types
"""
def _object_type_is_subclass(obj_type, class_or_seq, context=None):
if not isinstance(class_or_seq, (tuple, list)):
class_seq = (class_or_seq,)
else:
class_seq = class_or_seq
obj_type = object_type(node, context)
if obj_type is util.Uninferable:
return util.Uninferable
 
# Instances are not types
class_seq = [item if not isinstance(item, bases.Instance)
else util.Uninferable for item in class_seq]
# strict compatibility with isinstance
# isinstance(1, (int, 1)) evaluates to true
# isinstance(1, (1, int)) raises TypeError
# strict compatibility with issubclass
# issubclass(type, (object, 1)) evaluates to true
# issubclass(object, (1, type)) raises TypeError
for klass in class_seq:
if klass is util.Uninferable:
raise exceptions.AstroidTypeError(
"isinstance() arg 2 must be a type or tuple of types")
raise exceptions.AstroidTypeError("arg 2 must be a type or tuple of types")
for obj_subclass in obj_type.mro():
if obj_subclass == klass:
return True
return False
 
 
def object_isinstance(node, class_or_seq, context=None):
"""Check if a node 'isinstance' any node in class_or_seq
:param node: A given node
:param class_or_seq: Union[nodes.NodeNG, Sequence[nodes.NodeNG]]
:rtype: bool
:raises AstroidTypeError: if the given ``classes_or_seq`` are not types
"""
obj_type = object_type(node, context)
if obj_type is util.Uninferable:
return util.Uninferable
return _object_type_is_subclass(obj_type, class_or_seq, context=context)
def object_issubclass(node, class_or_seq, context=None):
"""Check if a type is a subclass of any node in class_or_seq
:param node: A given node
:param class_or_seq: Union[Nodes.NodeNG, Sequence[nodes.NodeNG]]
:rtype: bool
:raises AstroidTypeError: if the given ``classes_or_seq`` are not types
:raises AstroidError: if the type of the given node cannot be inferred
or its type's mro doesn't work
"""
if not isinstance(node, nodes.ClassDef):
raise TypeError("{node} needs to be a ClassDef node".format(node=node))
return _object_type_is_subclass(node, class_or_seq, context=context)
def safe_infer(node, context=None):
"""Return the inferred value for the given node.
 
Loading
Loading
@@ -199,3 +222,46 @@ def class_instance_as_index(node):
except exceptions.InferenceError:
pass
return None
def object_len(node, context=None):
"""Infer length of given node object
:param Union[nodes.ClassDef, nodes.Instance] node:
:param node: Node to infer length of
:raises AstroidTypeError: If an invalid node is returned
from __len__ method or no __len__ method exists
:raises InferenceError: If the given node cannot be inferred
or if multiple nodes are inferred
:rtype int: Integer length of node
"""
from astroid.objects import FrozenSet
inferred_node = safe_infer(node, context=context)
if inferred_node is None or inferred_node is util.Uninferable:
raise exceptions.InferenceError(node=node)
if inferred_node.qname() in ('builtins.str', 'builtins.bytes'):
return len(inferred_node.value)
if isinstance(inferred_node, (nodes.List, nodes.Set, nodes.Tuple, FrozenSet)):
return len(inferred_node.elts)
if isinstance(inferred_node, nodes.Dict):
return len(inferred_node.items)
try:
node_type = object_type(inferred_node, context=context)
len_call = next(node_type.igetattr("__len__", context=context))
except exceptions.AttributeInferenceError:
raise exceptions.AstroidTypeError(
"object of type '{}' has no len()"
.format(len_call.pytype()))
try:
result_of_len = next(len_call.infer_call_result(node, context))
# Remove StopIteration catch when #507 is fixed
except StopIteration:
raise exceptions.InferenceError(node=node)
if (isinstance(result_of_len, nodes.Const) and result_of_len.pytype() == "builtins.int"):
return result_of_len.value
else:
raise exceptions.AstroidTypeError(
"'{}' object cannot be interpreted as an integer"
.format(result_of_len))
Loading
Loading
@@ -1002,6 +1002,87 @@ class TestIsinstanceInference:
_get_result_node('isinstance(something, int)')
 
 
class TestIssubclassBrain:
"""Test issubclass() builtin inference"""
def test_type_type(self):
assert _get_result("issubclass(type, type)") == "True"
def test_object_type(self):
assert _get_result("issubclass(object, type)") == "False"
def test_type_object(self):
assert _get_result("issubclass(type, object)") == "True"
def test_issubclass_same_class(self):
assert _get_result("issubclass(int, int)") == "True"
def test_issubclass_not_the_same_class(self):
assert _get_result("issubclass(str, int)") == "False"
def test_issubclass_object_true(self):
assert _get_result("""
class Bar(object):
pass
issubclass(Bar, object)
""") == "True"
def test_issubclass_same_user_defined_class(self):
assert _get_result("""
class Bar(object):
pass
issubclass(Bar, Bar)
""") == "True"
def test_issubclass_different_user_defined_classes(self):
assert _get_result("""
class Foo(object):
pass
class Bar(object):
pass
issubclass(Bar, Foo)
""") == "False"
def test_issubclass_type_false(self):
assert _get_result("""
class Bar(object):
pass
issubclass(Bar, type)
""") == "False"
def test_isinstance_tuple_argument(self):
"""obj just has to be a subclass of ANY class/type on the right"""
assert _get_result("issubclass(int, (str, int))") == "True"
def test_isinstance_object_true2(self):
assert _get_result("""
class Bar(type):
pass
issubclass(Bar, object)
""") == "True"
def test_issubclass_short_circuit(self):
"""issubclasss allows bad type short-circuting"""
assert _get_result("issubclass(int, (int, 1))") == "True"
def test_uninferable_bad_type(self):
"""The second argument must be a class or a tuple of classes"""
# Should I subclass
with pytest.raises(astroid.InferenceError):
_get_result_node("issubclass(int, 1)")
def test_uninferable_keywords(self):
"""issubclass does not allow keywords"""
with pytest.raises(astroid.InferenceError):
_get_result_node("issubclass(int, class_or_tuple=int)")
def test_too_many_args(self):
"""issubclass must have two arguments"""
with pytest.raises(astroid.InferenceError):
_get_result_node("issubclass(int, int, str)")
def _get_result_node(code):
node = next(astroid.extract_node(code).infer())
return node
Loading
Loading
@@ -1011,5 +1092,144 @@ def _get_result(code):
return _get_result_node(code).as_string()
 
 
class TestLenBuiltinInference:
def test_len_list(self):
# Uses .elts
node = astroid.extract_node("""
len(['a','b','c'])
""")
node = next(node.infer())
assert node.as_string() == '3'
assert isinstance(node, nodes.Const)
def test_len_tuple(self):
node = astroid.extract_node("""
len(('a','b','c'))
""")
node = next(node.infer())
assert node.as_string() == '3'
def test_len_var(self):
# Make sure argument is inferred
node = astroid.extract_node("""
a = [1,2,'a','b','c']
len(a)
""")
node = next(node.infer())
assert node.as_string() == '5'
def test_len_dict(self):
# Uses .items
node = astroid.extract_node("""
a = {'a': 1, 'b': 2}
len(a)
""")
node = next(node.infer())
assert node.as_string() == '2'
def test_len_set(self):
node = astroid.extract_node("""
len({'a'})
""")
inferred_node = next(node.infer())
assert inferred_node.as_string() == '1'
def test_len_object(self):
"""Test len with objects that implement the len protocol"""
node = astroid.extract_node("""
class A:
def __len__(self):
return 57
len(A())
""")
inferred_node = next(node.infer())
assert inferred_node.as_string() == '57'
def test_len_class_with_metaclass(self):
"""Make sure proper len method is located"""
cls_node, inst_node = astroid.extract_node("""
class F2(type):
def __new__(cls, name, bases, attrs):
return super().__new__(cls, name, bases, {})
def __len__(self):
return 57
class F(metaclass=F2):
def __len__(self):
return 4
len(F) #@
len(F()) #@
""")
assert next(cls_node.infer()).as_string() == '57'
assert next(inst_node.infer()).as_string() == '4'
def test_len_object_failure(self):
"""If taking the length of a class, do not use an instance method"""
node = astroid.extract_node("""
class F:
def __len__(self):
return 57
len(F)
""")
with pytest.raises(astroid.InferenceError):
next(node.infer())
def test_len_string(self):
node = astroid.extract_node("""
len("uwu")
""")
assert next(node.infer()).as_string() == "3"
def test_len_generator_failure(self):
node = astroid.extract_node("""
def gen():
yield 'a'
yield 'b'
len(gen())
""")
with pytest.raises(astroid.InferenceError):
next(node.infer())
def test_len_failure_missing_variable(self):
node = astroid.extract_node("""
len(a)
""")
with pytest.raises(astroid.InferenceError):
next(node.infer())
def test_len_bytes(self):
node = astroid.extract_node("""
len(b'uwu')
""")
assert next(node.infer()).as_string() == '3'
@pytest.mark.xfail(reason="Can't retrieve subclassed type value ")
def test_int_subclass_result(self):
"""I am unable to figure out the value of an
object which subclasses int"""
node = astroid.extract_node("""
class IntSubclass(int):
pass
class F:
def __len__(self):
return IntSubclass(5)
len(F())
""")
assert next(node.infer()).as_string() == '5'
@pytest.mark.xfail(reason="Can't use list special astroid fields")
def test_int_subclass_argument(self):
"""I am unable to access the length of a object which
subclasses list"""
node = astroid.extract_node("""
class ListSubclass(list):
pass
len(ListSubclass([1,2,3,4,4]))
""")
assert next(node.infer()).as_string() == '5'
if __name__ == '__main__':
unittest.main()
Loading
Loading
@@ -12,6 +12,7 @@
# serve to show the default.
 
import sys, os
from datetime import datetime
 
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
Loading
Loading
@@ -48,7 +49,8 @@ master_doc = 'index'
 
# General information about the project.
project = u'Astroid'
copyright = u'2003-2016, Logilab'
current_year = datetime.utcnow().year
copyright = u'2003-{year}, Logilab, PyCQA and contributors'.format(year=current_year)
 
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Loading
Loading