forked from openkylin/astroid
3182 lines
97 KiB
Python
3182 lines
97 KiB
Python
# Copyright (c) 2013-2014 Google, Inc.
|
|
# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
|
|
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
|
|
# Copyright (c) 2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
|
|
# Copyright (c) 2015 raylu <lurayl@gmail.com>
|
|
# Copyright (c) 2015 Philip Lorenz <philip@bithub.de>
|
|
# Copyright (c) 2016 Florian Bruhin <me@the-compiler.org>
|
|
# Copyright (c) 2017-2018, 2020-2021 hippo91 <guillaume.peillex@gmail.com>
|
|
# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
|
|
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
|
|
# Copyright (c) 2017 David Euresti <github@euresti.com>
|
|
# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
|
|
# Copyright (c) 2018, 2021 Nick Drozd <nicholasdrozd@gmail.com>
|
|
# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz>
|
|
# Copyright (c) 2018 David Poirier <david-poirier-csn@users.noreply.github.com>
|
|
# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
|
|
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
|
|
# Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com>
|
|
# Copyright (c) 2018 Ahmed Azzaoui <ahmed.azzaoui@engie.com>
|
|
# Copyright (c) 2019-2020 Bryce Guinta <bryce.guinta@protonmail.com>
|
|
# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
|
|
# Copyright (c) 2019 Tomas Novak <ext.Tomas.Novak@skoda-auto.cz>
|
|
# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
|
|
# Copyright (c) 2019 Grygorii Iermolenko <gyermolenko@gmail.com>
|
|
# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
|
|
# Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
|
|
# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
|
# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
|
|
# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
|
|
# Copyright (c) 2021 Joshua Cannon <joshua.cannon@ni.com>
|
|
# Copyright (c) 2021 Craig Franklin <craigjfranklin@gmail.com>
|
|
# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
|
|
# Copyright (c) 2021 Jonathan Striebel <jstriebel@users.noreply.github.com>
|
|
# Copyright (c) 2021 Dimitri Prybysh <dmand@yandex.ru>
|
|
# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
|
|
# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
|
|
# Copyright (c) 2021 Alphadelta14 <alpha@alphaservcomputing.solutions>
|
|
# Copyright (c) 2021 Tim Martin <tim@asymptotic.co.uk>
|
|
# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
|
|
# Copyright (c) 2021 Artsiom Kaval <lezeroq@gmail.com>
|
|
# Copyright (c) 2021 Damien Baty <damien@damienbaty.com>
|
|
|
|
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
|
|
# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
|
|
|
|
"""Tests for basic functionality in astroid.brain."""
|
|
import io
|
|
import queue
|
|
import re
|
|
import sys
|
|
import unittest
|
|
from typing import Any, List
|
|
|
|
import pytest
|
|
|
|
import astroid
|
|
from astroid import MANAGER, bases, builder, nodes, objects, test_utils, util
|
|
from astroid.bases import Instance
|
|
from astroid.const import PY37_PLUS
|
|
from astroid.exceptions import AttributeInferenceError, InferenceError
|
|
from astroid.nodes.node_classes import Const
|
|
from astroid.nodes.scoped_nodes import ClassDef
|
|
|
|
try:
|
|
import multiprocessing # pylint: disable=unused-import
|
|
|
|
HAS_MULTIPROCESSING = True
|
|
except ImportError:
|
|
HAS_MULTIPROCESSING = False
|
|
|
|
|
|
try:
|
|
import nose # pylint: disable=unused-import
|
|
|
|
HAS_NOSE = True
|
|
except ImportError:
|
|
HAS_NOSE = False
|
|
|
|
try:
|
|
import dateutil # pylint: disable=unused-import
|
|
|
|
HAS_DATEUTIL = True
|
|
except ImportError:
|
|
HAS_DATEUTIL = False
|
|
|
|
try:
|
|
import attr as attr_module # pylint: disable=unused-import
|
|
|
|
HAS_ATTR = True
|
|
except ImportError:
|
|
HAS_ATTR = False
|
|
|
|
try:
|
|
import six # pylint: disable=unused-import
|
|
|
|
HAS_SIX = True
|
|
except ImportError:
|
|
HAS_SIX = False
|
|
|
|
|
|
def assertEqualMro(klass: ClassDef, expected_mro: List[str]) -> None:
|
|
"""Check mro names."""
|
|
assert [member.qname() for member in klass.mro()] == expected_mro
|
|
|
|
|
|
class HashlibTest(unittest.TestCase):
|
|
def _assert_hashlib_class(self, class_obj: ClassDef) -> None:
|
|
self.assertIn("update", class_obj)
|
|
self.assertIn("digest", class_obj)
|
|
self.assertIn("hexdigest", class_obj)
|
|
self.assertIn("block_size", class_obj)
|
|
self.assertIn("digest_size", class_obj)
|
|
self.assertEqual(len(class_obj["__init__"].args.args), 2)
|
|
self.assertEqual(len(class_obj["__init__"].args.defaults), 1)
|
|
self.assertEqual(len(class_obj["update"].args.args), 2)
|
|
self.assertEqual(len(class_obj["digest"].args.args), 1)
|
|
self.assertEqual(len(class_obj["hexdigest"].args.args), 1)
|
|
|
|
def test_hashlib(self) -> None:
|
|
"""Tests that brain extensions for hashlib work."""
|
|
hashlib_module = MANAGER.ast_from_module_name("hashlib")
|
|
for class_name in ("md5", "sha1"):
|
|
class_obj = hashlib_module[class_name]
|
|
self._assert_hashlib_class(class_obj)
|
|
|
|
def test_hashlib_py36(self) -> None:
|
|
hashlib_module = MANAGER.ast_from_module_name("hashlib")
|
|
for class_name in ("sha3_224", "sha3_512", "shake_128"):
|
|
class_obj = hashlib_module[class_name]
|
|
self._assert_hashlib_class(class_obj)
|
|
for class_name in ("blake2b", "blake2s"):
|
|
class_obj = hashlib_module[class_name]
|
|
self.assertEqual(len(class_obj["__init__"].args.args), 2)
|
|
|
|
|
|
class CollectionsDequeTests(unittest.TestCase):
|
|
def _inferred_queue_instance(self) -> Instance:
|
|
node = builder.extract_node(
|
|
"""
|
|
import collections
|
|
q = collections.deque([])
|
|
q
|
|
"""
|
|
)
|
|
return next(node.infer())
|
|
|
|
def test_deque(self) -> None:
|
|
inferred = self._inferred_queue_instance()
|
|
self.assertTrue(inferred.getattr("__len__"))
|
|
|
|
def test_deque_py35methods(self) -> None:
|
|
inferred = self._inferred_queue_instance()
|
|
self.assertIn("copy", inferred.locals)
|
|
self.assertIn("insert", inferred.locals)
|
|
self.assertIn("index", inferred.locals)
|
|
|
|
@test_utils.require_version(maxver="3.8")
|
|
def test_deque_not_py39methods(self):
|
|
inferred = self._inferred_queue_instance()
|
|
with self.assertRaises(AttributeInferenceError):
|
|
inferred.getattr("__class_getitem__")
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_deque_py39methods(self):
|
|
inferred = self._inferred_queue_instance()
|
|
self.assertTrue(inferred.getattr("__class_getitem__"))
|
|
|
|
|
|
class OrderedDictTest(unittest.TestCase):
|
|
def _inferred_ordered_dict_instance(self) -> Instance:
|
|
node = builder.extract_node(
|
|
"""
|
|
import collections
|
|
d = collections.OrderedDict()
|
|
d
|
|
"""
|
|
)
|
|
return next(node.infer())
|
|
|
|
def test_ordered_dict_py34method(self) -> None:
|
|
inferred = self._inferred_ordered_dict_instance()
|
|
self.assertIn("move_to_end", inferred.locals)
|
|
|
|
|
|
class NamedTupleTest(unittest.TestCase):
|
|
def test_namedtuple_base(self) -> None:
|
|
klass = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
|
|
class X(namedtuple("X", ["a", "b", "c"])):
|
|
pass
|
|
"""
|
|
)
|
|
assert isinstance(klass, nodes.ClassDef)
|
|
self.assertEqual(
|
|
[anc.name for anc in klass.ancestors()], ["X", "tuple", "object"]
|
|
)
|
|
for anc in klass.ancestors():
|
|
self.assertFalse(anc.parent is None)
|
|
|
|
def test_namedtuple_inference(self) -> None:
|
|
klass = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
|
|
name = "X"
|
|
fields = ["a", "b", "c"]
|
|
class X(namedtuple(name, fields)):
|
|
pass
|
|
"""
|
|
)
|
|
assert isinstance(klass, nodes.ClassDef)
|
|
base = next(base for base in klass.ancestors() if base.name == "X")
|
|
self.assertSetEqual({"a", "b", "c"}, set(base.instance_attrs))
|
|
|
|
def test_namedtuple_inference_failure(self) -> None:
|
|
klass = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
|
|
def foo(fields):
|
|
return __(namedtuple("foo", fields))
|
|
"""
|
|
)
|
|
self.assertIs(util.Uninferable, next(klass.infer()))
|
|
|
|
def test_namedtuple_advanced_inference(self) -> None:
|
|
# urlparse return an object of class ParseResult, which has a
|
|
# namedtuple call and a mixin as base classes
|
|
result = builder.extract_node(
|
|
"""
|
|
from urllib.parse import urlparse
|
|
|
|
result = __(urlparse('gopher://'))
|
|
"""
|
|
)
|
|
instance = next(result.infer())
|
|
self.assertGreaterEqual(len(instance.getattr("scheme")), 1)
|
|
self.assertGreaterEqual(len(instance.getattr("port")), 1)
|
|
with self.assertRaises(AttributeInferenceError):
|
|
instance.getattr("foo")
|
|
self.assertGreaterEqual(len(instance.getattr("geturl")), 1)
|
|
self.assertEqual(instance.name, "ParseResult")
|
|
|
|
def test_namedtuple_instance_attrs(self) -> None:
|
|
result = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
namedtuple('a', 'a b c')(1, 2, 3) #@
|
|
"""
|
|
)
|
|
inferred = next(result.infer())
|
|
for name, attr in inferred.instance_attrs.items():
|
|
self.assertEqual(attr[0].attrname, name)
|
|
|
|
def test_namedtuple_uninferable_fields(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
x = [A] * 2
|
|
from collections import namedtuple
|
|
l = namedtuple('a', x)
|
|
l(1)
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred)
|
|
|
|
def test_namedtuple_access_class_fields(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", "field other")
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIn("field", inferred.locals)
|
|
self.assertIn("other", inferred.locals)
|
|
|
|
def test_namedtuple_rename_keywords(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", "abc def", rename=True)
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIn("abc", inferred.locals)
|
|
self.assertIn("_1", inferred.locals)
|
|
|
|
def test_namedtuple_rename_duplicates(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", "abc abc abc", rename=True)
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIn("abc", inferred.locals)
|
|
self.assertIn("_1", inferred.locals)
|
|
self.assertIn("_2", inferred.locals)
|
|
|
|
def test_namedtuple_rename_uninferable(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", "a b c", rename=UNINFERABLE)
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIn("a", inferred.locals)
|
|
self.assertIn("b", inferred.locals)
|
|
self.assertIn("c", inferred.locals)
|
|
|
|
def test_namedtuple_func_form(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple(typename="Tuple", field_names="a b c", rename=UNINFERABLE)
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertEqual(inferred.name, "Tuple")
|
|
self.assertIn("a", inferred.locals)
|
|
self.assertIn("b", inferred.locals)
|
|
self.assertIn("c", inferred.locals)
|
|
|
|
def test_namedtuple_func_form_args_and_kwargs(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", field_names="a b c", rename=UNINFERABLE)
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertEqual(inferred.name, "Tuple")
|
|
self.assertIn("a", inferred.locals)
|
|
self.assertIn("b", inferred.locals)
|
|
self.assertIn("c", inferred.locals)
|
|
|
|
def test_namedtuple_bases_are_actually_names_not_nodes(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", field_names="a b c", rename=UNINFERABLE)
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, astroid.ClassDef)
|
|
self.assertIsInstance(inferred.bases[0], astroid.Name)
|
|
self.assertEqual(inferred.bases[0].name, "tuple")
|
|
|
|
def test_invalid_label_does_not_crash_inference(self) -> None:
|
|
code = """
|
|
import collections
|
|
a = collections.namedtuple( 'a', ['b c'] )
|
|
a
|
|
"""
|
|
node = builder.extract_node(code)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.ClassDef)
|
|
assert "b" not in inferred.locals
|
|
assert "c" not in inferred.locals
|
|
|
|
def test_no_rename_duplicates_does_not_crash_inference(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", "abc abc")
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred) # would raise ValueError
|
|
|
|
def test_no_rename_keywords_does_not_crash_inference(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", "abc def")
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred) # would raise ValueError
|
|
|
|
def test_no_rename_nonident_does_not_crash_inference(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", "123 456")
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred) # would raise ValueError
|
|
|
|
def test_no_rename_underscore_does_not_crash_inference(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", "_1")
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred) # would raise ValueError
|
|
|
|
def test_invalid_typename_does_not_crash_inference(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("123", "abc")
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred) # would raise ValueError
|
|
|
|
def test_keyword_typename_does_not_crash_inference(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("while", "abc")
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred) # would raise ValueError
|
|
|
|
def test_typeerror_does_not_crash_inference(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
Tuple = namedtuple("Tuple", [123, 456])
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
# namedtuple converts all arguments to strings so these should be too
|
|
# and catch on the isidentifier() check
|
|
self.assertIs(util.Uninferable, inferred)
|
|
|
|
def test_pathological_str_does_not_crash_inference(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import namedtuple
|
|
class Invalid:
|
|
def __str__(self):
|
|
return 123 # will raise TypeError
|
|
Tuple = namedtuple("Tuple", [Invalid()])
|
|
Tuple #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred)
|
|
|
|
|
|
class DefaultDictTest(unittest.TestCase):
|
|
def test_1(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from collections import defaultdict
|
|
|
|
X = defaultdict(int)
|
|
X[0]
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(util.Uninferable, inferred)
|
|
|
|
|
|
class ModuleExtenderTest(unittest.TestCase):
|
|
def test_extension_modules(self) -> None:
|
|
transformer = MANAGER._transform
|
|
for extender, _ in transformer.transforms[nodes.Module]:
|
|
n = nodes.Module("__main__", None)
|
|
extender(n)
|
|
|
|
|
|
@unittest.skipUnless(HAS_NOSE, "This test requires nose library.")
|
|
class NoseBrainTest(unittest.TestCase):
|
|
def test_nose_tools(self):
|
|
methods = builder.extract_node(
|
|
"""
|
|
from nose.tools import assert_equal
|
|
from nose.tools import assert_equals
|
|
from nose.tools import assert_true
|
|
assert_equal = assert_equal #@
|
|
assert_true = assert_true #@
|
|
assert_equals = assert_equals #@
|
|
"""
|
|
)
|
|
assert isinstance(methods, list)
|
|
assert_equal = next(methods[0].value.infer())
|
|
assert_true = next(methods[1].value.infer())
|
|
assert_equals = next(methods[2].value.infer())
|
|
|
|
self.assertIsInstance(assert_equal, astroid.BoundMethod)
|
|
self.assertIsInstance(assert_true, astroid.BoundMethod)
|
|
self.assertIsInstance(assert_equals, astroid.BoundMethod)
|
|
self.assertEqual(assert_equal.qname(), "unittest.case.TestCase.assertEqual")
|
|
self.assertEqual(assert_true.qname(), "unittest.case.TestCase.assertTrue")
|
|
self.assertEqual(assert_equals.qname(), "unittest.case.TestCase.assertEqual")
|
|
|
|
|
|
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
|
|
class SixBrainTest(unittest.TestCase):
|
|
def test_attribute_access(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
import six
|
|
six.moves.http_client #@
|
|
six.moves.urllib_parse #@
|
|
six.moves.urllib_error #@
|
|
six.moves.urllib.request #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
http_client = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(http_client, nodes.Module)
|
|
self.assertEqual(http_client.name, "http.client")
|
|
|
|
urllib_parse = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(urllib_parse, nodes.Module)
|
|
self.assertEqual(urllib_parse.name, "urllib.parse")
|
|
urljoin = next(urllib_parse.igetattr("urljoin"))
|
|
urlencode = next(urllib_parse.igetattr("urlencode"))
|
|
self.assertIsInstance(urljoin, nodes.FunctionDef)
|
|
self.assertEqual(urljoin.qname(), "urllib.parse.urljoin")
|
|
self.assertIsInstance(urlencode, nodes.FunctionDef)
|
|
self.assertEqual(urlencode.qname(), "urllib.parse.urlencode")
|
|
|
|
urllib_error = next(ast_nodes[2].infer())
|
|
self.assertIsInstance(urllib_error, nodes.Module)
|
|
self.assertEqual(urllib_error.name, "urllib.error")
|
|
urlerror = next(urllib_error.igetattr("URLError"))
|
|
self.assertIsInstance(urlerror, nodes.ClassDef)
|
|
content_too_short = next(urllib_error.igetattr("ContentTooShortError"))
|
|
self.assertIsInstance(content_too_short, nodes.ClassDef)
|
|
|
|
urllib_request = next(ast_nodes[3].infer())
|
|
self.assertIsInstance(urllib_request, nodes.Module)
|
|
self.assertEqual(urllib_request.name, "urllib.request")
|
|
urlopen = next(urllib_request.igetattr("urlopen"))
|
|
urlretrieve = next(urllib_request.igetattr("urlretrieve"))
|
|
self.assertIsInstance(urlopen, nodes.FunctionDef)
|
|
self.assertEqual(urlopen.qname(), "urllib.request.urlopen")
|
|
self.assertIsInstance(urlretrieve, nodes.FunctionDef)
|
|
self.assertEqual(urlretrieve.qname(), "urllib.request.urlretrieve")
|
|
|
|
def test_from_imports(self) -> None:
|
|
ast_node = builder.extract_node(
|
|
"""
|
|
from six.moves import http_client
|
|
http_client.HTTPSConnection #@
|
|
"""
|
|
)
|
|
inferred = next(ast_node.infer())
|
|
self.assertIsInstance(inferred, nodes.ClassDef)
|
|
qname = "http.client.HTTPSConnection"
|
|
self.assertEqual(inferred.qname(), qname)
|
|
|
|
def test_from_submodule_imports(self) -> None:
|
|
"""Make sure ulrlib submodules can be imported from
|
|
|
|
See PyCQA/pylint#1640 for relevant issue
|
|
"""
|
|
ast_node = builder.extract_node(
|
|
"""
|
|
from six.moves.urllib.parse import urlparse
|
|
urlparse #@
|
|
"""
|
|
)
|
|
inferred = next(ast_node.infer())
|
|
self.assertIsInstance(inferred, nodes.FunctionDef)
|
|
|
|
def test_with_metaclass_subclasses_inheritance(self) -> None:
|
|
ast_node = builder.extract_node(
|
|
"""
|
|
class A(type):
|
|
def test(cls):
|
|
return cls
|
|
|
|
class C:
|
|
pass
|
|
|
|
import six
|
|
class B(six.with_metaclass(A, C)):
|
|
pass
|
|
|
|
B #@
|
|
"""
|
|
)
|
|
inferred = next(ast_node.infer())
|
|
self.assertIsInstance(inferred, nodes.ClassDef)
|
|
self.assertEqual(inferred.name, "B")
|
|
self.assertIsInstance(inferred.bases[0], nodes.Call)
|
|
ancestors = tuple(inferred.ancestors())
|
|
self.assertIsInstance(ancestors[0], nodes.ClassDef)
|
|
self.assertEqual(ancestors[0].name, "C")
|
|
self.assertIsInstance(ancestors[1], nodes.ClassDef)
|
|
self.assertEqual(ancestors[1].name, "object")
|
|
|
|
def test_six_with_metaclass_with_additional_transform(self) -> None:
|
|
def transform_class(cls: Any) -> ClassDef:
|
|
if cls.name == "A":
|
|
cls._test_transform = 314
|
|
return cls
|
|
|
|
MANAGER.register_transform(nodes.ClassDef, transform_class)
|
|
try:
|
|
ast_node = builder.extract_node(
|
|
"""
|
|
import six
|
|
class A(six.with_metaclass(type, object)):
|
|
pass
|
|
|
|
A #@
|
|
"""
|
|
)
|
|
inferred = next(ast_node.infer())
|
|
assert getattr(inferred, "_test_transform", None) == 314
|
|
finally:
|
|
MANAGER.unregister_transform(nodes.ClassDef, transform_class)
|
|
|
|
|
|
@unittest.skipUnless(
|
|
HAS_MULTIPROCESSING,
|
|
"multiprocesing is required for this test, but "
|
|
"on some platforms it is missing "
|
|
"(Jython for instance)",
|
|
)
|
|
class MultiprocessingBrainTest(unittest.TestCase):
|
|
def test_multiprocessing_module_attributes(self) -> None:
|
|
# Test that module attributes are working,
|
|
# especially on Python 3.4+, where they are obtained
|
|
# from a context.
|
|
module = builder.extract_node(
|
|
"""
|
|
import multiprocessing
|
|
"""
|
|
)
|
|
assert isinstance(module, nodes.Import)
|
|
module = module.do_import_module("multiprocessing")
|
|
cpu_count = next(module.igetattr("cpu_count"))
|
|
self.assertIsInstance(cpu_count, astroid.BoundMethod)
|
|
|
|
def test_module_name(self) -> None:
|
|
module = builder.extract_node(
|
|
"""
|
|
import multiprocessing
|
|
multiprocessing.SyncManager()
|
|
"""
|
|
)
|
|
inferred_sync_mgr = next(module.infer())
|
|
module = inferred_sync_mgr.root()
|
|
self.assertEqual(module.name, "multiprocessing.managers")
|
|
|
|
def test_multiprocessing_manager(self) -> None:
|
|
# Test that we have the proper attributes
|
|
# for a multiprocessing.managers.SyncManager
|
|
module = builder.parse(
|
|
"""
|
|
import multiprocessing
|
|
manager = multiprocessing.Manager()
|
|
queue = manager.Queue()
|
|
joinable_queue = manager.JoinableQueue()
|
|
event = manager.Event()
|
|
rlock = manager.RLock()
|
|
bounded_semaphore = manager.BoundedSemaphore()
|
|
condition = manager.Condition()
|
|
barrier = manager.Barrier()
|
|
pool = manager.Pool()
|
|
list = manager.list()
|
|
dict = manager.dict()
|
|
value = manager.Value()
|
|
array = manager.Array()
|
|
namespace = manager.Namespace()
|
|
"""
|
|
)
|
|
ast_queue = next(module["queue"].infer())
|
|
self.assertEqual(ast_queue.qname(), f"{queue.__name__}.Queue")
|
|
|
|
joinable_queue = next(module["joinable_queue"].infer())
|
|
self.assertEqual(joinable_queue.qname(), f"{queue.__name__}.Queue")
|
|
|
|
event = next(module["event"].infer())
|
|
event_name = "threading.Event"
|
|
self.assertEqual(event.qname(), event_name)
|
|
|
|
rlock = next(module["rlock"].infer())
|
|
rlock_name = "threading._RLock"
|
|
self.assertEqual(rlock.qname(), rlock_name)
|
|
|
|
bounded_semaphore = next(module["bounded_semaphore"].infer())
|
|
semaphore_name = "threading.BoundedSemaphore"
|
|
self.assertEqual(bounded_semaphore.qname(), semaphore_name)
|
|
|
|
pool = next(module["pool"].infer())
|
|
pool_name = "multiprocessing.pool.Pool"
|
|
self.assertEqual(pool.qname(), pool_name)
|
|
|
|
for attr in ("list", "dict"):
|
|
obj = next(module[attr].infer())
|
|
self.assertEqual(obj.qname(), f"builtins.{attr}")
|
|
|
|
# pypy's implementation of array.__spec__ return None. This causes problems for this inference.
|
|
if not hasattr(sys, "pypy_version_info"):
|
|
array = next(module["array"].infer())
|
|
self.assertEqual(array.qname(), "array.array")
|
|
|
|
manager = next(module["manager"].infer())
|
|
# Verify that we have these attributes
|
|
self.assertTrue(manager.getattr("start"))
|
|
self.assertTrue(manager.getattr("shutdown"))
|
|
|
|
|
|
class ThreadingBrainTest(unittest.TestCase):
|
|
def test_lock(self) -> None:
|
|
lock_instance = builder.extract_node(
|
|
"""
|
|
import threading
|
|
threading.Lock()
|
|
"""
|
|
)
|
|
inferred = next(lock_instance.infer())
|
|
self.assert_is_valid_lock(inferred)
|
|
|
|
acquire_method = inferred.getattr("acquire")[0]
|
|
parameters = [param.name for param in acquire_method.args.args[1:]]
|
|
assert parameters == ["blocking", "timeout"]
|
|
|
|
assert inferred.getattr("locked")
|
|
|
|
def test_rlock(self) -> None:
|
|
self._test_lock_object("RLock")
|
|
|
|
def test_semaphore(self) -> None:
|
|
self._test_lock_object("Semaphore")
|
|
|
|
def test_boundedsemaphore(self) -> None:
|
|
self._test_lock_object("BoundedSemaphore")
|
|
|
|
def _test_lock_object(self, object_name: str) -> None:
|
|
lock_instance = builder.extract_node(
|
|
f"""
|
|
import threading
|
|
threading.{object_name}()
|
|
"""
|
|
)
|
|
inferred = next(lock_instance.infer())
|
|
self.assert_is_valid_lock(inferred)
|
|
|
|
def assert_is_valid_lock(self, inferred: Instance) -> None:
|
|
self.assertIsInstance(inferred, astroid.Instance)
|
|
self.assertEqual(inferred.root().name, "threading")
|
|
for method in ("acquire", "release", "__enter__", "__exit__"):
|
|
self.assertIsInstance(next(inferred.igetattr(method)), astroid.BoundMethod)
|
|
|
|
|
|
class EnumBrainTest(unittest.TestCase):
|
|
def test_simple_enum(self) -> None:
|
|
module = builder.parse(
|
|
"""
|
|
import enum
|
|
|
|
class MyEnum(enum.Enum):
|
|
one = "one"
|
|
two = "two"
|
|
|
|
def mymethod(self, x):
|
|
return 5
|
|
|
|
"""
|
|
)
|
|
|
|
enumeration = next(module["MyEnum"].infer())
|
|
one = enumeration["one"]
|
|
self.assertEqual(one.pytype(), ".MyEnum.one")
|
|
|
|
for propname in ("name", "value"):
|
|
prop = next(iter(one.getattr(propname)))
|
|
self.assertIn("builtins.property", prop.decoratornames())
|
|
|
|
meth = one.getattr("mymethod")[0]
|
|
self.assertIsInstance(meth, astroid.FunctionDef)
|
|
|
|
def test_looks_like_enum_false_positive(self) -> None:
|
|
# Test that a class named Enumeration is not considered a builtin enum.
|
|
module = builder.parse(
|
|
"""
|
|
class Enumeration(object):
|
|
def __init__(self, name, enum_list):
|
|
pass
|
|
test = 42
|
|
"""
|
|
)
|
|
enumeration = module["Enumeration"]
|
|
test = next(enumeration.igetattr("test"))
|
|
self.assertEqual(test.value, 42)
|
|
|
|
def test_user_enum_false_positive(self) -> None:
|
|
# Test that a user-defined class named Enum is not considered a builtin enum.
|
|
ast_node = astroid.extract_node(
|
|
"""
|
|
class Enum:
|
|
pass
|
|
|
|
class Color(Enum):
|
|
red = 1
|
|
|
|
Color.red #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_node, nodes.NodeNG)
|
|
inferred = ast_node.inferred()
|
|
self.assertEqual(len(inferred), 1)
|
|
self.assertIsInstance(inferred[0], astroid.Const)
|
|
self.assertEqual(inferred[0].value, 1)
|
|
|
|
def test_ignores_with_nodes_from_body_of_enum(self) -> None:
|
|
code = """
|
|
import enum
|
|
|
|
class Error(enum.Enum):
|
|
Foo = "foo"
|
|
Bar = "bar"
|
|
with "error" as err:
|
|
pass
|
|
"""
|
|
node = builder.extract_node(code)
|
|
inferred = next(node.infer())
|
|
assert "err" in inferred.locals
|
|
assert len(inferred.locals["err"]) == 1
|
|
|
|
def test_enum_multiple_base_classes(self) -> None:
|
|
module = builder.parse(
|
|
"""
|
|
import enum
|
|
|
|
class Mixin:
|
|
pass
|
|
|
|
class MyEnum(Mixin, enum.Enum):
|
|
one = 1
|
|
"""
|
|
)
|
|
enumeration = next(module["MyEnum"].infer())
|
|
one = enumeration["one"]
|
|
|
|
clazz = one.getattr("__class__")[0]
|
|
self.assertTrue(
|
|
clazz.is_subtype_of(".Mixin"),
|
|
"Enum instance should share base classes with generating class",
|
|
)
|
|
|
|
def test_int_enum(self) -> None:
|
|
module = builder.parse(
|
|
"""
|
|
import enum
|
|
|
|
class MyEnum(enum.IntEnum):
|
|
one = 1
|
|
"""
|
|
)
|
|
|
|
enumeration = next(module["MyEnum"].infer())
|
|
one = enumeration["one"]
|
|
|
|
clazz = one.getattr("__class__")[0]
|
|
self.assertTrue(
|
|
clazz.is_subtype_of("builtins.int"),
|
|
"IntEnum based enums should be a subtype of int",
|
|
)
|
|
|
|
def test_enum_func_form_is_class_not_instance(self) -> None:
|
|
cls, instance = builder.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
f = Enum('Audience', ['a', 'b', 'c'])
|
|
f #@
|
|
f(1) #@
|
|
"""
|
|
)
|
|
inferred_cls = next(cls.infer())
|
|
self.assertIsInstance(inferred_cls, bases.Instance)
|
|
inferred_instance = next(instance.infer())
|
|
self.assertIsInstance(inferred_instance, bases.Instance)
|
|
self.assertIsInstance(next(inferred_instance.igetattr("name")), nodes.Const)
|
|
self.assertIsInstance(next(inferred_instance.igetattr("value")), nodes.Const)
|
|
|
|
def test_enum_func_form_iterable(self) -> None:
|
|
instance = builder.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
Animal = Enum('Animal', 'ant bee cat dog')
|
|
Animal
|
|
"""
|
|
)
|
|
inferred = next(instance.infer())
|
|
self.assertIsInstance(inferred, astroid.Instance)
|
|
self.assertTrue(inferred.getattr("__iter__"))
|
|
|
|
def test_enum_func_form_subscriptable(self) -> None:
|
|
instance, name = builder.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
Animal = Enum('Animal', 'ant bee cat dog')
|
|
Animal['ant'] #@
|
|
Animal['ant'].name #@
|
|
"""
|
|
)
|
|
instance = next(instance.infer())
|
|
self.assertIsInstance(instance, astroid.Instance)
|
|
|
|
inferred = next(name.infer())
|
|
self.assertIsInstance(inferred, astroid.Const)
|
|
|
|
def test_enum_func_form_has_dunder_members(self) -> None:
|
|
instance = builder.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
Animal = Enum('Animal', 'ant bee cat dog')
|
|
for i in Animal.__members__:
|
|
i #@
|
|
"""
|
|
)
|
|
instance = next(instance.infer())
|
|
self.assertIsInstance(instance, astroid.Const)
|
|
self.assertIsInstance(instance.value, str)
|
|
|
|
def test_infer_enum_value_as_the_right_type(self) -> None:
|
|
string_value, int_value = builder.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
class A(Enum):
|
|
a = 'a'
|
|
b = 1
|
|
A.a.value #@
|
|
A.b.value #@
|
|
"""
|
|
)
|
|
inferred_string = string_value.inferred()
|
|
assert any(
|
|
isinstance(elem, astroid.Const) and elem.value == "a"
|
|
for elem in inferred_string
|
|
)
|
|
|
|
inferred_int = int_value.inferred()
|
|
assert any(
|
|
isinstance(elem, astroid.Const) and elem.value == 1 for elem in inferred_int
|
|
)
|
|
|
|
def test_mingled_single_and_double_quotes_does_not_crash(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
class A(Enum):
|
|
a = 'x"y"'
|
|
A.a.value #@
|
|
"""
|
|
)
|
|
inferred_string = next(node.infer())
|
|
assert inferred_string.value == 'x"y"'
|
|
|
|
def test_special_characters_does_not_crash(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
import enum
|
|
class Example(enum.Enum):
|
|
NULL = '\\N{NULL}'
|
|
Example.NULL.value
|
|
"""
|
|
)
|
|
inferred_string = next(node.infer())
|
|
assert inferred_string.value == "\N{NULL}"
|
|
|
|
def test_dont_crash_on_for_loops_in_body(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
|
|
class Commands(IntEnum):
|
|
_ignore_ = 'Commands index'
|
|
_init_ = 'value string'
|
|
|
|
BEL = 0x07, 'Bell'
|
|
Commands = vars()
|
|
for index in range(4):
|
|
Commands[f'DC{index + 1}'] = 0x11 + index, f'Device Control {index + 1}'
|
|
|
|
Commands
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.ClassDef)
|
|
|
|
def test_enum_tuple_list_values(self) -> None:
|
|
tuple_node, list_node = builder.extract_node(
|
|
"""
|
|
import enum
|
|
|
|
class MyEnum(enum.Enum):
|
|
a = (1, 2)
|
|
b = [2, 4]
|
|
MyEnum.a.value #@
|
|
MyEnum.b.value #@
|
|
"""
|
|
)
|
|
inferred_tuple_node = next(tuple_node.infer())
|
|
inferred_list_node = next(list_node.infer())
|
|
assert isinstance(inferred_tuple_node, astroid.Tuple)
|
|
assert isinstance(inferred_list_node, astroid.List)
|
|
assert inferred_tuple_node.as_string() == "(1, 2)"
|
|
assert inferred_list_node.as_string() == "[2, 4]"
|
|
|
|
def test_enum_starred_is_skipped(self) -> None:
|
|
code = """
|
|
from enum import Enum
|
|
class ContentType(Enum):
|
|
TEXT, PHOTO, VIDEO, GIF, YOUTUBE, *_ = [1, 2, 3, 4, 5, 6]
|
|
ContentType.TEXT #@
|
|
"""
|
|
node = astroid.extract_node(code)
|
|
next(node.infer())
|
|
|
|
def test_enum_name_is_str_on_self(self) -> None:
|
|
code = """
|
|
from enum import Enum
|
|
class TestEnum(Enum):
|
|
def func(self):
|
|
self.name #@
|
|
self.value #@
|
|
TestEnum.name #@
|
|
TestEnum.value #@
|
|
"""
|
|
i_name, i_value, c_name, c_value = astroid.extract_node(code)
|
|
|
|
# <instance>.name should be a string, <class>.name should be a property (that
|
|
# forwards the lookup to __getattr__)
|
|
inferred = next(i_name.infer())
|
|
assert isinstance(inferred, nodes.Const)
|
|
assert inferred.pytype() == "builtins.str"
|
|
inferred = next(c_name.infer())
|
|
assert isinstance(inferred, objects.Property)
|
|
|
|
# Inferring .value should not raise InferenceError. It is probably Uninferable
|
|
# but we don't particularly care
|
|
next(i_value.infer())
|
|
next(c_value.infer())
|
|
|
|
def test_enum_name_and_value_members_override_dynamicclassattr(self) -> None:
|
|
code = """
|
|
from enum import Enum
|
|
class TrickyEnum(Enum):
|
|
name = 1
|
|
value = 2
|
|
|
|
def func(self):
|
|
self.name #@
|
|
self.value #@
|
|
TrickyEnum.name #@
|
|
TrickyEnum.value #@
|
|
"""
|
|
i_name, i_value, c_name, c_value = astroid.extract_node(code)
|
|
|
|
# All of these cases should be inferred as enum members
|
|
inferred = next(i_name.infer())
|
|
assert isinstance(inferred, bases.Instance)
|
|
assert inferred.pytype() == ".TrickyEnum.name"
|
|
inferred = next(c_name.infer())
|
|
assert isinstance(inferred, bases.Instance)
|
|
assert inferred.pytype() == ".TrickyEnum.name"
|
|
inferred = next(i_value.infer())
|
|
assert isinstance(inferred, bases.Instance)
|
|
assert inferred.pytype() == ".TrickyEnum.value"
|
|
inferred = next(c_value.infer())
|
|
assert isinstance(inferred, bases.Instance)
|
|
assert inferred.pytype() == ".TrickyEnum.value"
|
|
|
|
def test_enum_subclass_member_name(self) -> None:
|
|
ast_node = astroid.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
|
|
class EnumSubclass(Enum):
|
|
pass
|
|
|
|
class Color(EnumSubclass):
|
|
red = 1
|
|
|
|
Color.red.name #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_node, nodes.NodeNG)
|
|
inferred = ast_node.inferred()
|
|
self.assertEqual(len(inferred), 1)
|
|
self.assertIsInstance(inferred[0], astroid.Const)
|
|
self.assertEqual(inferred[0].value, "red")
|
|
|
|
def test_enum_subclass_member_value(self) -> None:
|
|
ast_node = astroid.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
|
|
class EnumSubclass(Enum):
|
|
pass
|
|
|
|
class Color(EnumSubclass):
|
|
red = 1
|
|
|
|
Color.red.value #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_node, nodes.NodeNG)
|
|
inferred = ast_node.inferred()
|
|
self.assertEqual(len(inferred), 1)
|
|
self.assertIsInstance(inferred[0], astroid.Const)
|
|
self.assertEqual(inferred[0].value, 1)
|
|
|
|
def test_enum_subclass_member_method(self) -> None:
|
|
# See Pylint issue #2626
|
|
ast_node = astroid.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
|
|
class EnumSubclass(Enum):
|
|
def hello_pylint(self) -> str:
|
|
return self.name
|
|
|
|
class Color(EnumSubclass):
|
|
red = 1
|
|
|
|
Color.red.hello_pylint() #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_node, nodes.NodeNG)
|
|
inferred = ast_node.inferred()
|
|
self.assertEqual(len(inferred), 1)
|
|
self.assertIsInstance(inferred[0], astroid.Const)
|
|
self.assertEqual(inferred[0].value, "red")
|
|
|
|
def test_enum_subclass_different_modules(self) -> None:
|
|
# See Pylint issue #2626
|
|
astroid.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
|
|
class EnumSubclass(Enum):
|
|
pass
|
|
""",
|
|
"a",
|
|
)
|
|
ast_node = astroid.extract_node(
|
|
"""
|
|
from a import EnumSubclass
|
|
|
|
class Color(EnumSubclass):
|
|
red = 1
|
|
|
|
Color.red.value #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_node, nodes.NodeNG)
|
|
inferred = ast_node.inferred()
|
|
self.assertEqual(len(inferred), 1)
|
|
self.assertIsInstance(inferred[0], astroid.Const)
|
|
self.assertEqual(inferred[0].value, 1)
|
|
|
|
def test_members_member_ignored(self) -> None:
|
|
ast_node = builder.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
class Animal(Enum):
|
|
a = 1
|
|
__members__ = {}
|
|
Animal.__members__ #@
|
|
"""
|
|
)
|
|
|
|
inferred = next(ast_node.infer())
|
|
self.assertIsInstance(inferred, astroid.Dict)
|
|
self.assertTrue(inferred.locals)
|
|
|
|
|
|
@unittest.skipUnless(HAS_DATEUTIL, "This test requires the dateutil library.")
|
|
class DateutilBrainTest(unittest.TestCase):
|
|
def test_parser(self):
|
|
module = builder.parse(
|
|
"""
|
|
from dateutil.parser import parse
|
|
d = parse('2000-01-01')
|
|
"""
|
|
)
|
|
d_type = next(module["d"].infer())
|
|
self.assertEqual(d_type.qname(), "datetime.datetime")
|
|
|
|
|
|
class PytestBrainTest(unittest.TestCase):
|
|
def test_pytest(self) -> None:
|
|
ast_node = builder.extract_node(
|
|
"""
|
|
import pytest
|
|
pytest #@
|
|
"""
|
|
)
|
|
module = next(ast_node.infer())
|
|
attrs = [
|
|
"deprecated_call",
|
|
"warns",
|
|
"exit",
|
|
"fail",
|
|
"skip",
|
|
"importorskip",
|
|
"xfail",
|
|
"mark",
|
|
"raises",
|
|
"freeze_includes",
|
|
"set_trace",
|
|
"fixture",
|
|
"yield_fixture",
|
|
]
|
|
for attr in attrs:
|
|
self.assertIn(attr, module)
|
|
|
|
|
|
def streams_are_fine():
|
|
"""Check if streams are being overwritten,
|
|
for example, by pytest
|
|
|
|
stream inference will not work if they are overwritten
|
|
|
|
PY3 only
|
|
"""
|
|
return all(isinstance(s, io.IOBase) for s in (sys.stdout, sys.stderr, sys.stdin))
|
|
|
|
|
|
class IOBrainTest(unittest.TestCase):
|
|
@unittest.skipUnless(
|
|
streams_are_fine(),
|
|
"Needs Python 3 io model / doesn't work with plain pytest."
|
|
"use pytest -s for this test to work",
|
|
)
|
|
def test_sys_streams(self):
|
|
for name in ("stdout", "stderr", "stdin"):
|
|
node = astroid.extract_node(
|
|
f"""
|
|
import sys
|
|
sys.{name}
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
buffer_attr = next(inferred.igetattr("buffer"))
|
|
self.assertIsInstance(buffer_attr, astroid.Instance)
|
|
self.assertEqual(buffer_attr.name, "BufferedWriter")
|
|
raw = next(buffer_attr.igetattr("raw"))
|
|
self.assertIsInstance(raw, astroid.Instance)
|
|
self.assertEqual(raw.name, "FileIO")
|
|
|
|
|
|
@test_utils.require_version("3.9")
|
|
class TypeBrain(unittest.TestCase):
|
|
def test_type_subscript(self):
|
|
"""
|
|
Check that type object has the __class_getitem__ method
|
|
when it is used as a subscript
|
|
"""
|
|
src = builder.extract_node(
|
|
"""
|
|
a: type[int] = int
|
|
"""
|
|
)
|
|
val_inf = src.annotation.value.inferred()[0]
|
|
self.assertIsInstance(val_inf, astroid.ClassDef)
|
|
self.assertEqual(val_inf.name, "type")
|
|
meth_inf = val_inf.getattr("__class_getitem__")[0]
|
|
self.assertIsInstance(meth_inf, astroid.FunctionDef)
|
|
|
|
def test_invalid_type_subscript(self):
|
|
"""
|
|
Check that a type (str for example) that inherits
|
|
from type does not have __class_getitem__ method even
|
|
when it is used as a subscript
|
|
"""
|
|
src = builder.extract_node(
|
|
"""
|
|
a: str[int] = "abc"
|
|
"""
|
|
)
|
|
val_inf = src.annotation.value.inferred()[0]
|
|
self.assertIsInstance(val_inf, astroid.ClassDef)
|
|
self.assertEqual(val_inf.name, "str")
|
|
with self.assertRaises(AttributeInferenceError):
|
|
# pylint: disable=expression-not-assigned
|
|
# noinspection PyStatementEffect
|
|
val_inf.getattr("__class_getitem__")[0]
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_builtin_subscriptable(self):
|
|
"""
|
|
Starting with python3.9 builtin type such as list are subscriptable
|
|
"""
|
|
for typename in ("tuple", "list", "dict", "set", "frozenset"):
|
|
src = f"""
|
|
{typename:s}[int]
|
|
"""
|
|
right_node = builder.extract_node(src)
|
|
inferred = next(right_node.infer())
|
|
self.assertIsInstance(inferred, nodes.ClassDef)
|
|
self.assertIsInstance(inferred.getattr("__iter__")[0], nodes.FunctionDef)
|
|
|
|
|
|
def check_metaclass_is_abc(node: nodes.ClassDef):
|
|
meta = node.metaclass()
|
|
assert isinstance(meta, nodes.ClassDef)
|
|
assert meta.name == "ABCMeta"
|
|
|
|
|
|
class CollectionsBrain(unittest.TestCase):
|
|
def test_collections_object_not_subscriptable(self) -> None:
|
|
"""
|
|
Test that unsubscriptable types are detected
|
|
Hashable is not subscriptable even with python39
|
|
"""
|
|
wrong_node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
collections.abc.Hashable[int]
|
|
"""
|
|
)
|
|
with self.assertRaises(InferenceError):
|
|
next(wrong_node.infer())
|
|
right_node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
collections.abc.Hashable
|
|
"""
|
|
)
|
|
inferred = next(right_node.infer())
|
|
check_metaclass_is_abc(inferred)
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
"_collections_abc.Hashable",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
with self.assertRaises(AttributeInferenceError):
|
|
inferred.getattr("__class_getitem__")
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_collections_object_subscriptable(self):
|
|
"""Starting with python39 some object of collections module are subscriptable. Test one of them"""
|
|
right_node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
collections.abc.MutableSet[int]
|
|
"""
|
|
)
|
|
inferred = next(right_node.infer())
|
|
check_metaclass_is_abc(inferred)
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
"_collections_abc.MutableSet",
|
|
"_collections_abc.Set",
|
|
"_collections_abc.Collection",
|
|
"_collections_abc.Sized",
|
|
"_collections_abc.Iterable",
|
|
"_collections_abc.Container",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
self.assertIsInstance(
|
|
inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
|
|
)
|
|
|
|
@test_utils.require_version(maxver="3.9")
|
|
def test_collections_object_not_yet_subscriptable(self):
|
|
"""
|
|
Test that unsubscriptable types are detected as such.
|
|
Until python39 MutableSet of the collections module is not subscriptable.
|
|
"""
|
|
wrong_node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
collections.abc.MutableSet[int]
|
|
"""
|
|
)
|
|
with self.assertRaises(InferenceError):
|
|
next(wrong_node.infer())
|
|
right_node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
collections.abc.MutableSet
|
|
"""
|
|
)
|
|
inferred = next(right_node.infer())
|
|
check_metaclass_is_abc(inferred)
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
"_collections_abc.MutableSet",
|
|
"_collections_abc.Set",
|
|
"_collections_abc.Collection",
|
|
"_collections_abc.Sized",
|
|
"_collections_abc.Iterable",
|
|
"_collections_abc.Container",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
with self.assertRaises(AttributeInferenceError):
|
|
inferred.getattr("__class_getitem__")
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_collections_object_subscriptable_2(self):
|
|
"""Starting with python39 Iterator in the collection.abc module is subscriptable"""
|
|
node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
class Derived(collections.abc.Iterator[int]):
|
|
pass
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
check_metaclass_is_abc(inferred)
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
".Derived",
|
|
"_collections_abc.Iterator",
|
|
"_collections_abc.Iterable",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
|
|
@test_utils.require_version(maxver="3.9")
|
|
def test_collections_object_not_yet_subscriptable_2(self):
|
|
"""Before python39 Iterator in the collection.abc module is not subscriptable"""
|
|
node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
collections.abc.Iterator[int]
|
|
"""
|
|
)
|
|
with self.assertRaises(InferenceError):
|
|
next(node.infer())
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_collections_object_subscriptable_3(self):
|
|
"""With python39 ByteString class of the colletions module is subscritable (but not the same class from typing module)"""
|
|
right_node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
collections.abc.ByteString[int]
|
|
"""
|
|
)
|
|
inferred = next(right_node.infer())
|
|
check_metaclass_is_abc(inferred)
|
|
self.assertIsInstance(
|
|
inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
|
|
)
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_collections_object_subscriptable_4(self):
|
|
"""Multiple inheritance with subscriptable collection class"""
|
|
node = builder.extract_node(
|
|
"""
|
|
import collections.abc
|
|
class Derived(collections.abc.Hashable, collections.abc.Iterator[int]):
|
|
pass
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
".Derived",
|
|
"_collections_abc.Hashable",
|
|
"_collections_abc.Iterator",
|
|
"_collections_abc.Iterable",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
|
|
|
|
class TypingBrain(unittest.TestCase):
|
|
def test_namedtuple_base(self) -> None:
|
|
klass = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
|
|
class X(NamedTuple("X", [("a", int), ("b", str), ("c", bytes)])):
|
|
pass
|
|
"""
|
|
)
|
|
self.assertEqual(
|
|
[anc.name for anc in klass.ancestors()], ["X", "tuple", "object"]
|
|
)
|
|
for anc in klass.ancestors():
|
|
self.assertFalse(anc.parent is None)
|
|
|
|
def test_namedtuple_can_correctly_access_methods(self) -> None:
|
|
klass, called = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
|
|
class X(NamedTuple): #@
|
|
a: int
|
|
b: int
|
|
def as_string(self):
|
|
return '%s' % self.a
|
|
def as_integer(self):
|
|
return 2 + 3
|
|
X().as_integer() #@
|
|
"""
|
|
)
|
|
self.assertEqual(len(klass.getattr("as_string")), 1)
|
|
inferred = next(called.infer())
|
|
self.assertIsInstance(inferred, astroid.Const)
|
|
self.assertEqual(inferred.value, 5)
|
|
|
|
def test_namedtuple_inference(self) -> None:
|
|
klass = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
|
|
class X(NamedTuple("X", [("a", int), ("b", str), ("c", bytes)])):
|
|
pass
|
|
"""
|
|
)
|
|
base = next(base for base in klass.ancestors() if base.name == "X")
|
|
self.assertSetEqual({"a", "b", "c"}, set(base.instance_attrs))
|
|
|
|
def test_namedtuple_inference_nonliteral(self) -> None:
|
|
# Note: NamedTuples in mypy only work with literals.
|
|
klass = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
|
|
name = "X"
|
|
fields = [("a", int), ("b", str), ("c", bytes)]
|
|
NamedTuple(name, fields)
|
|
"""
|
|
)
|
|
inferred = next(klass.infer())
|
|
self.assertIsInstance(inferred, astroid.Instance)
|
|
self.assertEqual(inferred.qname(), "typing.NamedTuple")
|
|
|
|
def test_namedtuple_instance_attrs(self) -> None:
|
|
result = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
NamedTuple("A", [("a", int), ("b", str), ("c", bytes)])(1, 2, 3) #@
|
|
"""
|
|
)
|
|
inferred = next(result.infer())
|
|
for name, attr in inferred.instance_attrs.items():
|
|
self.assertEqual(attr[0].attrname, name)
|
|
|
|
def test_namedtuple_simple(self) -> None:
|
|
result = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
NamedTuple("A", [("a", int), ("b", str), ("c", bytes)])
|
|
"""
|
|
)
|
|
inferred = next(result.infer())
|
|
self.assertIsInstance(inferred, nodes.ClassDef)
|
|
self.assertSetEqual({"a", "b", "c"}, set(inferred.instance_attrs))
|
|
|
|
def test_namedtuple_few_args(self) -> None:
|
|
result = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
NamedTuple("A")
|
|
"""
|
|
)
|
|
inferred = next(result.infer())
|
|
self.assertIsInstance(inferred, astroid.Instance)
|
|
self.assertEqual(inferred.qname(), "typing.NamedTuple")
|
|
|
|
def test_namedtuple_few_fields(self) -> None:
|
|
result = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
NamedTuple("A", [("a",), ("b", str), ("c", bytes)])
|
|
"""
|
|
)
|
|
inferred = next(result.infer())
|
|
self.assertIsInstance(inferred, astroid.Instance)
|
|
self.assertEqual(inferred.qname(), "typing.NamedTuple")
|
|
|
|
def test_namedtuple_class_form(self) -> None:
|
|
result = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
|
|
class Example(NamedTuple):
|
|
CLASS_ATTR = "class_attr"
|
|
mything: int
|
|
|
|
Example(mything=1)
|
|
"""
|
|
)
|
|
inferred = next(result.infer())
|
|
self.assertIsInstance(inferred, astroid.Instance)
|
|
|
|
class_attr = inferred.getattr("CLASS_ATTR")[0]
|
|
self.assertIsInstance(class_attr, astroid.AssignName)
|
|
const = next(class_attr.infer())
|
|
self.assertEqual(const.value, "class_attr")
|
|
|
|
def test_namedtuple_inferred_as_class(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
NamedTuple
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, nodes.ClassDef)
|
|
assert inferred.name == "NamedTuple"
|
|
|
|
def test_namedtuple_bug_pylint_4383(self) -> None:
|
|
"""Inference of 'NamedTuple' function shouldn't cause InferenceError.
|
|
|
|
https://github.com/PyCQA/pylint/issues/4383
|
|
"""
|
|
node = builder.extract_node(
|
|
"""
|
|
if True:
|
|
def NamedTuple():
|
|
pass
|
|
NamedTuple
|
|
"""
|
|
)
|
|
next(node.infer())
|
|
|
|
def test_typing_types(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
from typing import TypeVar, Iterable, Tuple, NewType, Dict, Union
|
|
TypeVar('MyTypeVar', int, float, complex) #@
|
|
Iterable[Tuple[MyTypeVar, MyTypeVar]] #@
|
|
TypeVar('AnyStr', str, bytes) #@
|
|
NewType('UserId', str) #@
|
|
Dict[str, str] #@
|
|
Union[int, str] #@
|
|
"""
|
|
)
|
|
for node in ast_nodes:
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, nodes.ClassDef, node.as_string())
|
|
|
|
def test_namedtuple_nested_class(self):
|
|
result = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
|
|
class Example(NamedTuple):
|
|
class Foo:
|
|
bar = "bar"
|
|
|
|
Example
|
|
"""
|
|
)
|
|
inferred = next(result.infer())
|
|
self.assertIsInstance(inferred, astroid.ClassDef)
|
|
|
|
class_def_attr = inferred.getattr("Foo")[0]
|
|
self.assertIsInstance(class_def_attr, astroid.ClassDef)
|
|
attr_def = class_def_attr.getattr("bar")[0]
|
|
attr = next(attr_def.infer())
|
|
self.assertEqual(attr.value, "bar")
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_tuple_type(self):
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import Tuple
|
|
Tuple[int, int]
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, nodes.ClassDef)
|
|
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
|
|
assert inferred.qname() == "typing.Tuple"
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_callable_type(self):
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import Callable, Any
|
|
Callable[..., Any]
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, nodes.ClassDef)
|
|
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
|
|
assert inferred.qname() == "typing.Callable"
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_typing_generic_subscriptable(self):
|
|
"""Test typing.Generic is subscriptable with __class_getitem__ (added in PY37)"""
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import Generic, TypeVar
|
|
T = TypeVar('T')
|
|
Generic[T]
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, nodes.ClassDef)
|
|
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_typing_annotated_subscriptable(self):
|
|
"""Test typing.Annotated is subscriptable with __class_getitem__"""
|
|
node = builder.extract_node(
|
|
"""
|
|
import typing
|
|
typing.Annotated[str, "data"]
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, nodes.ClassDef)
|
|
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_typing_generic_slots(self):
|
|
"""Test slots for Generic subclass."""
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import Generic, TypeVar
|
|
T = TypeVar('T')
|
|
class A(Generic[T]):
|
|
__slots__ = ['value']
|
|
def __init__(self, value):
|
|
self.value = value
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
slots = inferred.slots()
|
|
assert len(slots) == 1
|
|
assert isinstance(slots[0], nodes.Const)
|
|
assert slots[0].value == "value"
|
|
|
|
def test_has_dunder_args(self) -> None:
|
|
ast_node = builder.extract_node(
|
|
"""
|
|
from typing import Union
|
|
NumericTypes = Union[int, float]
|
|
NumericTypes.__args__ #@
|
|
"""
|
|
)
|
|
inferred = next(ast_node.infer())
|
|
assert isinstance(inferred, nodes.Tuple)
|
|
|
|
def test_typing_namedtuple_dont_crash_on_no_fields(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import NamedTuple
|
|
|
|
Bar = NamedTuple("bar", [])
|
|
|
|
Bar()
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, astroid.Instance)
|
|
|
|
@test_utils.require_version("3.8")
|
|
def test_typed_dict(self):
|
|
code = builder.extract_node(
|
|
"""
|
|
from typing import TypedDict
|
|
class CustomTD(TypedDict): #@
|
|
var: int
|
|
CustomTD(var=1) #@
|
|
"""
|
|
)
|
|
inferred_base = next(code[0].bases[0].infer())
|
|
assert isinstance(inferred_base, nodes.ClassDef)
|
|
assert inferred_base.qname() == "typing.TypedDict"
|
|
typedDict_base = next(inferred_base.bases[0].infer())
|
|
assert typedDict_base.qname() == "builtins.dict"
|
|
|
|
# Test TypedDict has `__call__` method
|
|
local_call = inferred_base.locals.get("__call__", None)
|
|
assert local_call and len(local_call) == 1
|
|
assert isinstance(local_call[0], nodes.Name) and local_call[0].name == "dict"
|
|
|
|
# Test TypedDict instance is callable
|
|
assert next(code[1].infer()).callable() is True
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_typing_alias_type(self):
|
|
"""
|
|
Test that the type aliased thanks to typing._alias function are
|
|
correctly inferred.
|
|
typing_alias function is introduced with python37
|
|
"""
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import TypeVar, MutableSet
|
|
|
|
T = TypeVar("T")
|
|
MutableSet[T]
|
|
|
|
class Derived1(MutableSet[T]):
|
|
pass
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
".Derived1",
|
|
"typing.MutableSet",
|
|
"_collections_abc.MutableSet",
|
|
"_collections_abc.Set",
|
|
"_collections_abc.Collection",
|
|
"_collections_abc.Sized",
|
|
"_collections_abc.Iterable",
|
|
"_collections_abc.Container",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
|
|
@test_utils.require_version(minver="3.7.2")
|
|
def test_typing_alias_type_2(self):
|
|
"""
|
|
Test that the type aliased thanks to typing._alias function are
|
|
correctly inferred.
|
|
typing_alias function is introduced with python37.
|
|
OrderedDict in the typing module appears only with python 3.7.2
|
|
"""
|
|
node = builder.extract_node(
|
|
"""
|
|
import typing
|
|
class Derived2(typing.OrderedDict[int, str]):
|
|
pass
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
".Derived2",
|
|
"typing.OrderedDict",
|
|
"collections.OrderedDict",
|
|
"builtins.dict",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_typing_object_not_subscriptable(self):
|
|
"""Hashable is not subscriptable"""
|
|
wrong_node = builder.extract_node(
|
|
"""
|
|
import typing
|
|
typing.Hashable[int]
|
|
"""
|
|
)
|
|
with self.assertRaises(InferenceError):
|
|
next(wrong_node.infer())
|
|
right_node = builder.extract_node(
|
|
"""
|
|
import typing
|
|
typing.Hashable
|
|
"""
|
|
)
|
|
inferred = next(right_node.infer())
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
"typing.Hashable",
|
|
"_collections_abc.Hashable",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
with self.assertRaises(AttributeInferenceError):
|
|
inferred.getattr("__class_getitem__")
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_typing_object_subscriptable(self):
|
|
"""Test that MutableSet is subscriptable"""
|
|
right_node = builder.extract_node(
|
|
"""
|
|
import typing
|
|
typing.MutableSet[int]
|
|
"""
|
|
)
|
|
inferred = next(right_node.infer())
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
"typing.MutableSet",
|
|
"_collections_abc.MutableSet",
|
|
"_collections_abc.Set",
|
|
"_collections_abc.Collection",
|
|
"_collections_abc.Sized",
|
|
"_collections_abc.Iterable",
|
|
"_collections_abc.Container",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
self.assertIsInstance(
|
|
inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
|
|
)
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_typing_object_subscriptable_2(self):
|
|
"""Multiple inheritance with subscriptable typing alias"""
|
|
node = builder.extract_node(
|
|
"""
|
|
import typing
|
|
class Derived(typing.Hashable, typing.Iterator[int]):
|
|
pass
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assertEqualMro(
|
|
inferred,
|
|
[
|
|
".Derived",
|
|
"typing.Hashable",
|
|
"_collections_abc.Hashable",
|
|
"typing.Iterator",
|
|
"_collections_abc.Iterator",
|
|
"_collections_abc.Iterable",
|
|
"builtins.object",
|
|
],
|
|
)
|
|
|
|
@test_utils.require_version(minver="3.7")
|
|
def test_typing_object_notsubscriptable_3(self):
|
|
"""Until python39 ByteString class of the typing module is not subscritable (whereas it is in the collections module)"""
|
|
right_node = builder.extract_node(
|
|
"""
|
|
import typing
|
|
typing.ByteString
|
|
"""
|
|
)
|
|
inferred = next(right_node.infer())
|
|
check_metaclass_is_abc(inferred)
|
|
with self.assertRaises(AttributeInferenceError):
|
|
self.assertIsInstance(
|
|
inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
|
|
)
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_typing_object_builtin_subscriptable(self):
|
|
"""
|
|
Test that builtins alias, such as typing.List, are subscriptable
|
|
"""
|
|
for typename in ("List", "Dict", "Set", "FrozenSet", "Tuple"):
|
|
src = f"""
|
|
import typing
|
|
typing.{typename:s}[int]
|
|
"""
|
|
right_node = builder.extract_node(src)
|
|
inferred = next(right_node.infer())
|
|
self.assertIsInstance(inferred, nodes.ClassDef)
|
|
self.assertIsInstance(inferred.getattr("__iter__")[0], nodes.FunctionDef)
|
|
|
|
@staticmethod
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_typing_type_subscriptable():
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import Type
|
|
Type[int]
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, nodes.ClassDef)
|
|
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
|
|
assert inferred.qname() == "typing.Type"
|
|
|
|
def test_typing_cast(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from typing import cast
|
|
class A:
|
|
pass
|
|
|
|
b = 42
|
|
a = cast(A, b)
|
|
a
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, nodes.Const)
|
|
assert inferred.value == 42
|
|
|
|
def test_typing_cast_attribute(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
import typing
|
|
class A:
|
|
pass
|
|
|
|
b = 42
|
|
a = typing.cast(A, b)
|
|
a
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, nodes.Const)
|
|
assert inferred.value == 42
|
|
|
|
|
|
class ReBrainTest(unittest.TestCase):
|
|
def test_regex_flags(self) -> None:
|
|
names = [name for name in dir(re) if name.isupper()]
|
|
re_ast = MANAGER.ast_from_module_name("re")
|
|
for name in names:
|
|
self.assertIn(name, re_ast)
|
|
self.assertEqual(next(re_ast[name].infer()).value, getattr(re, name))
|
|
|
|
@test_utils.require_version(minver="3.7", maxver="3.9")
|
|
def test_re_pattern_unsubscriptable(self):
|
|
"""
|
|
re.Pattern and re.Match are unsubscriptable until PY39.
|
|
re.Pattern and re.Match were added in PY37.
|
|
"""
|
|
right_node1 = builder.extract_node(
|
|
"""
|
|
import re
|
|
re.Pattern
|
|
"""
|
|
)
|
|
inferred1 = next(right_node1.infer())
|
|
assert isinstance(inferred1, nodes.ClassDef)
|
|
with self.assertRaises(AttributeInferenceError):
|
|
assert isinstance(
|
|
inferred1.getattr("__class_getitem__")[0], nodes.FunctionDef
|
|
)
|
|
|
|
right_node2 = builder.extract_node(
|
|
"""
|
|
import re
|
|
re.Pattern
|
|
"""
|
|
)
|
|
inferred2 = next(right_node2.infer())
|
|
assert isinstance(inferred2, nodes.ClassDef)
|
|
with self.assertRaises(AttributeInferenceError):
|
|
assert isinstance(
|
|
inferred2.getattr("__class_getitem__")[0], nodes.FunctionDef
|
|
)
|
|
|
|
wrong_node1 = builder.extract_node(
|
|
"""
|
|
import re
|
|
re.Pattern[int]
|
|
"""
|
|
)
|
|
with self.assertRaises(InferenceError):
|
|
next(wrong_node1.infer())
|
|
|
|
wrong_node2 = builder.extract_node(
|
|
"""
|
|
import re
|
|
re.Match[int]
|
|
"""
|
|
)
|
|
with self.assertRaises(InferenceError):
|
|
next(wrong_node2.infer())
|
|
|
|
@test_utils.require_version(minver="3.9")
|
|
def test_re_pattern_subscriptable(self):
|
|
"""Test re.Pattern and re.Match are subscriptable in PY39+"""
|
|
node1 = builder.extract_node(
|
|
"""
|
|
import re
|
|
re.Pattern[str]
|
|
"""
|
|
)
|
|
inferred1 = next(node1.infer())
|
|
assert isinstance(inferred1, nodes.ClassDef)
|
|
assert isinstance(inferred1.getattr("__class_getitem__")[0], nodes.FunctionDef)
|
|
|
|
node2 = builder.extract_node(
|
|
"""
|
|
import re
|
|
re.Match[str]
|
|
"""
|
|
)
|
|
inferred2 = next(node2.infer())
|
|
assert isinstance(inferred2, nodes.ClassDef)
|
|
assert isinstance(inferred2.getattr("__class_getitem__")[0], nodes.FunctionDef)
|
|
|
|
|
|
class BrainFStrings(unittest.TestCase):
|
|
def test_no_crash_on_const_reconstruction(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
max_width = 10
|
|
|
|
test1 = f'{" ":{max_width+4}}'
|
|
print(f'"{test1}"')
|
|
|
|
test2 = f'[{"7":>{max_width}}:0]'
|
|
test2
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIs(inferred, util.Uninferable)
|
|
|
|
|
|
class BrainNamedtupleAnnAssignTest(unittest.TestCase):
|
|
def test_no_crash_on_ann_assign_in_namedtuple(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
from enum import Enum
|
|
from typing import Optional
|
|
|
|
class A(Enum):
|
|
B: str = 'B'
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, nodes.ClassDef)
|
|
|
|
|
|
class BrainUUIDTest(unittest.TestCase):
|
|
def test_uuid_has_int_member(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
import uuid
|
|
u = uuid.UUID('{12345678-1234-5678-1234-567812345678}')
|
|
u.int
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, nodes.Const)
|
|
|
|
|
|
@unittest.skipUnless(HAS_ATTR, "These tests require the attr library")
|
|
class AttrsTest(unittest.TestCase):
|
|
def test_attr_transform(self) -> None:
|
|
module = astroid.parse(
|
|
"""
|
|
import attr
|
|
from attr import attrs, attrib, field
|
|
|
|
@attr.s
|
|
class Foo:
|
|
|
|
d = attr.ib(attr.Factory(dict))
|
|
|
|
f = Foo()
|
|
f.d['answer'] = 42
|
|
|
|
@attr.s(slots=True)
|
|
class Bar:
|
|
d = attr.ib(attr.Factory(dict))
|
|
|
|
g = Bar()
|
|
g.d['answer'] = 42
|
|
|
|
@attrs
|
|
class Bah:
|
|
d = attrib(attr.Factory(dict))
|
|
|
|
h = Bah()
|
|
h.d['answer'] = 42
|
|
|
|
@attr.attrs
|
|
class Bai:
|
|
d = attr.attrib(attr.Factory(dict))
|
|
|
|
i = Bai()
|
|
i.d['answer'] = 42
|
|
|
|
@attr.define
|
|
class Spam:
|
|
d = field(default=attr.Factory(dict))
|
|
|
|
j = Spam(d=1)
|
|
j.d['answer'] = 42
|
|
|
|
@attr.mutable
|
|
class Eggs:
|
|
d = attr.field(default=attr.Factory(dict))
|
|
|
|
k = Eggs(d=1)
|
|
k.d['answer'] = 42
|
|
|
|
@attr.frozen
|
|
class Eggs:
|
|
d = attr.field(default=attr.Factory(dict))
|
|
|
|
l = Eggs(d=1)
|
|
l.d['answer'] = 42
|
|
"""
|
|
)
|
|
|
|
for name in ("f", "g", "h", "i", "j", "k", "l"):
|
|
should_be_unknown = next(module.getattr(name)[0].infer()).getattr("d")[0]
|
|
self.assertIsInstance(should_be_unknown, astroid.Unknown)
|
|
|
|
def test_special_attributes(self) -> None:
|
|
"""Make sure special attrs attributes exist"""
|
|
|
|
code = """
|
|
import attr
|
|
|
|
@attr.s
|
|
class Foo:
|
|
pass
|
|
Foo()
|
|
"""
|
|
foo_inst = next(astroid.extract_node(code).infer())
|
|
[attr_node] = foo_inst.getattr("__attrs_attrs__")
|
|
# Prevents https://github.com/PyCQA/pylint/issues/1884
|
|
assert isinstance(attr_node, nodes.Unknown)
|
|
|
|
def test_dont_consider_assignments_but_without_attrs(self) -> None:
|
|
code = """
|
|
import attr
|
|
|
|
class Cls: pass
|
|
@attr.s
|
|
class Foo:
|
|
temp = Cls()
|
|
temp.prop = 5
|
|
bar_thing = attr.ib(default=temp)
|
|
Foo()
|
|
"""
|
|
next(astroid.extract_node(code).infer())
|
|
|
|
def test_attrs_with_annotation(self) -> None:
|
|
code = """
|
|
import attr
|
|
|
|
@attr.s
|
|
class Foo:
|
|
bar: int = attr.ib(default=5)
|
|
Foo()
|
|
"""
|
|
should_be_unknown = next(astroid.extract_node(code).infer()).getattr("bar")[0]
|
|
self.assertIsInstance(should_be_unknown, astroid.Unknown)
|
|
|
|
|
|
class RandomSampleTest(unittest.TestCase):
|
|
def test_inferred_successfully(self) -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
import random
|
|
random.sample([1, 2], 2) #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, astroid.List)
|
|
elems = sorted(elem.value for elem in inferred.elts)
|
|
self.assertEqual(elems, [1, 2])
|
|
|
|
def test_no_crash_on_evaluatedobject(self) -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
from random import sample
|
|
class A: pass
|
|
sample(list({1: A()}.values()), 1)"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.List)
|
|
assert len(inferred.elts) == 1
|
|
assert isinstance(inferred.elts[0], nodes.Call)
|
|
|
|
|
|
class SubprocessTest(unittest.TestCase):
|
|
"""Test subprocess brain"""
|
|
|
|
def test_subprocess_args(self) -> None:
|
|
"""Make sure the args attribute exists for Popen
|
|
|
|
Test for https://github.com/PyCQA/pylint/issues/1860"""
|
|
name = astroid.extract_node(
|
|
"""
|
|
import subprocess
|
|
p = subprocess.Popen(['ls'])
|
|
p #@
|
|
"""
|
|
)
|
|
[inst] = name.inferred()
|
|
self.assertIsInstance(next(inst.igetattr("args")), nodes.List)
|
|
|
|
def test_subprcess_check_output(self) -> None:
|
|
code = """
|
|
import subprocess
|
|
|
|
subprocess.check_output(['echo', 'hello']);
|
|
"""
|
|
node = astroid.extract_node(code)
|
|
inferred = next(node.infer())
|
|
# Can be either str or bytes
|
|
assert isinstance(inferred, astroid.Const)
|
|
assert isinstance(inferred.value, (str, bytes))
|
|
|
|
@test_utils.require_version("3.9")
|
|
def test_popen_does_not_have_class_getitem(self):
|
|
code = """import subprocess; subprocess.Popen"""
|
|
node = astroid.extract_node(code)
|
|
inferred = next(node.infer())
|
|
assert "__class_getitem__" in inferred
|
|
|
|
|
|
class TestIsinstanceInference:
|
|
"""Test isinstance builtin inference"""
|
|
|
|
def test_type_type(self) -> None:
|
|
assert _get_result("isinstance(type, type)") == "True"
|
|
|
|
def test_object_type(self) -> None:
|
|
assert _get_result("isinstance(object, type)") == "True"
|
|
|
|
def test_type_object(self) -> None:
|
|
assert _get_result("isinstance(type, object)") == "True"
|
|
|
|
def test_isinstance_int_true(self) -> None:
|
|
"""Make sure isinstance can check builtin int types"""
|
|
assert _get_result("isinstance(1, int)") == "True"
|
|
|
|
def test_isinstance_int_false(self) -> None:
|
|
assert _get_result("isinstance('a', int)") == "False"
|
|
|
|
def test_isinstance_object_true(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(object):
|
|
pass
|
|
isinstance(Bar(), object)
|
|
"""
|
|
)
|
|
== "True"
|
|
)
|
|
|
|
def test_isinstance_object_true3(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(object):
|
|
pass
|
|
isinstance(Bar(), Bar)
|
|
"""
|
|
)
|
|
== "True"
|
|
)
|
|
|
|
def test_isinstance_class_false(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Foo(object):
|
|
pass
|
|
class Bar(object):
|
|
pass
|
|
isinstance(Bar(), Foo)
|
|
"""
|
|
)
|
|
== "False"
|
|
)
|
|
|
|
def test_isinstance_type_false(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(object):
|
|
pass
|
|
isinstance(Bar(), type)
|
|
"""
|
|
)
|
|
== "False"
|
|
)
|
|
|
|
def test_isinstance_str_true(self) -> None:
|
|
"""Make sure isinstance can check builtin str types"""
|
|
assert _get_result("isinstance('a', str)") == "True"
|
|
|
|
def test_isinstance_str_false(self) -> None:
|
|
assert _get_result("isinstance(1, str)") == "False"
|
|
|
|
def test_isinstance_tuple_argument(self) -> None:
|
|
"""obj just has to be an instance of ANY class/type on the right"""
|
|
assert _get_result("isinstance(1, (str, int))") == "True"
|
|
|
|
def test_isinstance_type_false2(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
isinstance(1, type)
|
|
"""
|
|
)
|
|
== "False"
|
|
)
|
|
|
|
def test_isinstance_object_true2(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(type):
|
|
pass
|
|
mainbar = Bar("Bar", tuple(), {})
|
|
isinstance(mainbar, object)
|
|
"""
|
|
)
|
|
== "True"
|
|
)
|
|
|
|
def test_isinstance_type_true(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(type):
|
|
pass
|
|
mainbar = Bar("Bar", tuple(), {})
|
|
isinstance(mainbar, type)
|
|
"""
|
|
)
|
|
== "True"
|
|
)
|
|
|
|
def test_isinstance_edge_case(self) -> None:
|
|
"""isinstance allows bad type short-circuting"""
|
|
assert _get_result("isinstance(1, (int, 1))") == "True"
|
|
|
|
def test_uninferable_bad_type(self) -> None:
|
|
"""The second argument must be a class or a tuple of classes"""
|
|
with pytest.raises(InferenceError):
|
|
_get_result_node("isinstance(int, 1)")
|
|
|
|
def test_uninferable_keywords(self) -> None:
|
|
"""isinstance does not allow keywords"""
|
|
with pytest.raises(InferenceError):
|
|
_get_result_node("isinstance(1, class_or_tuple=int)")
|
|
|
|
def test_too_many_args(self) -> None:
|
|
"""isinstance must have two arguments"""
|
|
with pytest.raises(InferenceError):
|
|
_get_result_node("isinstance(1, int, str)")
|
|
|
|
def test_first_param_is_uninferable(self) -> None:
|
|
with pytest.raises(InferenceError):
|
|
_get_result_node("isinstance(something, int)")
|
|
|
|
|
|
class TestIssubclassBrain:
|
|
"""Test issubclass() builtin inference"""
|
|
|
|
def test_type_type(self) -> None:
|
|
assert _get_result("issubclass(type, type)") == "True"
|
|
|
|
def test_object_type(self) -> None:
|
|
assert _get_result("issubclass(object, type)") == "False"
|
|
|
|
def test_type_object(self) -> None:
|
|
assert _get_result("issubclass(type, object)") == "True"
|
|
|
|
def test_issubclass_same_class(self) -> None:
|
|
assert _get_result("issubclass(int, int)") == "True"
|
|
|
|
def test_issubclass_not_the_same_class(self) -> None:
|
|
assert _get_result("issubclass(str, int)") == "False"
|
|
|
|
def test_issubclass_object_true(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(object):
|
|
pass
|
|
issubclass(Bar, object)
|
|
"""
|
|
)
|
|
== "True"
|
|
)
|
|
|
|
def test_issubclass_same_user_defined_class(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(object):
|
|
pass
|
|
issubclass(Bar, Bar)
|
|
"""
|
|
)
|
|
== "True"
|
|
)
|
|
|
|
def test_issubclass_different_user_defined_classes(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Foo(object):
|
|
pass
|
|
class Bar(object):
|
|
pass
|
|
issubclass(Bar, Foo)
|
|
"""
|
|
)
|
|
== "False"
|
|
)
|
|
|
|
def test_issubclass_type_false(self) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(object):
|
|
pass
|
|
issubclass(Bar, type)
|
|
"""
|
|
)
|
|
== "False"
|
|
)
|
|
|
|
def test_isinstance_tuple_argument(self) -> None:
|
|
"""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) -> None:
|
|
assert (
|
|
_get_result(
|
|
"""
|
|
class Bar(type):
|
|
pass
|
|
issubclass(Bar, object)
|
|
"""
|
|
)
|
|
== "True"
|
|
)
|
|
|
|
def test_issubclass_short_circuit(self) -> None:
|
|
"""issubclasss allows bad type short-circuting"""
|
|
assert _get_result("issubclass(int, (int, 1))") == "True"
|
|
|
|
def test_uninferable_bad_type(self) -> None:
|
|
"""The second argument must be a class or a tuple of classes"""
|
|
# Should I subclass
|
|
with pytest.raises(InferenceError):
|
|
_get_result_node("issubclass(int, 1)")
|
|
|
|
def test_uninferable_keywords(self) -> None:
|
|
"""issubclass does not allow keywords"""
|
|
with pytest.raises(InferenceError):
|
|
_get_result_node("issubclass(int, class_or_tuple=int)")
|
|
|
|
def test_too_many_args(self) -> None:
|
|
"""issubclass must have two arguments"""
|
|
with pytest.raises(InferenceError):
|
|
_get_result_node("issubclass(int, int, str)")
|
|
|
|
|
|
def _get_result_node(code: str) -> Const:
|
|
node = next(astroid.extract_node(code).infer())
|
|
return node
|
|
|
|
|
|
def _get_result(code: str) -> str:
|
|
return _get_result_node(code).as_string()
|
|
|
|
|
|
class TestLenBuiltinInference:
|
|
def test_len_list(self) -> None:
|
|
# 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) -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
len(('a','b','c'))
|
|
"""
|
|
)
|
|
node = next(node.infer())
|
|
assert node.as_string() == "3"
|
|
|
|
def test_len_var(self) -> None:
|
|
# 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) -> None:
|
|
# 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) -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
len({'a'})
|
|
"""
|
|
)
|
|
inferred_node = next(node.infer())
|
|
assert inferred_node.as_string() == "1"
|
|
|
|
def test_len_object(self) -> None:
|
|
"""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) -> None:
|
|
"""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) -> None:
|
|
"""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(InferenceError):
|
|
next(node.infer())
|
|
|
|
def test_len_string(self) -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
len("uwu")
|
|
"""
|
|
)
|
|
assert next(node.infer()).as_string() == "3"
|
|
|
|
def test_len_generator_failure(self) -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
def gen():
|
|
yield 'a'
|
|
yield 'b'
|
|
len(gen())
|
|
"""
|
|
)
|
|
with pytest.raises(InferenceError):
|
|
next(node.infer())
|
|
|
|
def test_len_failure_missing_variable(self) -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
len(a)
|
|
"""
|
|
)
|
|
with pytest.raises(InferenceError):
|
|
next(node.infer())
|
|
|
|
def test_len_bytes(self) -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
len(b'uwu')
|
|
"""
|
|
)
|
|
assert next(node.infer()).as_string() == "3"
|
|
|
|
def test_int_subclass_result(self) -> None:
|
|
"""Check that a subclass of an int can still be inferred
|
|
|
|
This test does not properly infer the value passed to the
|
|
int subclass (5) but still returns a proper integer as we
|
|
fake the result of the `len()` call.
|
|
"""
|
|
node = astroid.extract_node(
|
|
"""
|
|
class IntSubclass(int):
|
|
pass
|
|
|
|
class F:
|
|
def __len__(self):
|
|
return IntSubclass(5)
|
|
len(F())
|
|
"""
|
|
)
|
|
assert next(node.infer()).as_string() == "0"
|
|
|
|
@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 an 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"
|
|
|
|
def test_len_builtin_inference_attribute_error_str(self) -> None:
|
|
"""Make sure len builtin doesn't raise an AttributeError
|
|
on instances of str or bytes
|
|
|
|
See https://github.com/PyCQA/pylint/issues/1942
|
|
"""
|
|
code = 'len(str("F"))'
|
|
try:
|
|
next(astroid.extract_node(code).infer())
|
|
except InferenceError:
|
|
pass
|
|
|
|
def test_len_builtin_inference_recursion_error_self_referential_attribute(
|
|
self,
|
|
) -> None:
|
|
"""Make sure len calls do not trigger
|
|
recursion errors for self referential assignment
|
|
|
|
See https://github.com/PyCQA/pylint/issues/2734
|
|
"""
|
|
code = """
|
|
class Data:
|
|
def __init__(self):
|
|
self.shape = []
|
|
|
|
data = Data()
|
|
data.shape = len(data.shape)
|
|
data.shape #@
|
|
"""
|
|
try:
|
|
astroid.extract_node(code).inferred()
|
|
except RecursionError:
|
|
pytest.fail("Inference call should not trigger a recursion error")
|
|
|
|
|
|
def test_infer_str() -> None:
|
|
ast_nodes = astroid.extract_node(
|
|
"""
|
|
str(s) #@
|
|
str('a') #@
|
|
str(some_object()) #@
|
|
"""
|
|
)
|
|
for node in ast_nodes:
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Const)
|
|
|
|
node = astroid.extract_node(
|
|
"""
|
|
str(s='') #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Instance)
|
|
assert inferred.qname() == "builtins.str"
|
|
|
|
|
|
def test_infer_int() -> None:
|
|
ast_nodes = astroid.extract_node(
|
|
"""
|
|
int(0) #@
|
|
int('1') #@
|
|
"""
|
|
)
|
|
for node in ast_nodes:
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Const)
|
|
|
|
ast_nodes = astroid.extract_node(
|
|
"""
|
|
int(s='') #@
|
|
int('2.5') #@
|
|
int('something else') #@
|
|
int(unknown) #@
|
|
int(b'a') #@
|
|
"""
|
|
)
|
|
for node in ast_nodes:
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Instance)
|
|
assert inferred.qname() == "builtins.int"
|
|
|
|
|
|
def test_infer_dict_from_keys() -> None:
|
|
bad_nodes = astroid.extract_node(
|
|
"""
|
|
dict.fromkeys() #@
|
|
dict.fromkeys(1, 2, 3) #@
|
|
dict.fromkeys(a=1) #@
|
|
"""
|
|
)
|
|
for node in bad_nodes:
|
|
with pytest.raises(InferenceError):
|
|
next(node.infer())
|
|
|
|
# Test uninferable values
|
|
good_nodes = astroid.extract_node(
|
|
"""
|
|
from unknown import Unknown
|
|
dict.fromkeys(some_value) #@
|
|
dict.fromkeys(some_other_value) #@
|
|
dict.fromkeys([Unknown(), Unknown()]) #@
|
|
dict.fromkeys([Unknown(), Unknown()]) #@
|
|
"""
|
|
)
|
|
for node in good_nodes:
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Dict)
|
|
assert inferred.items == []
|
|
|
|
# Test inferable values
|
|
|
|
# from a dictionary's keys
|
|
from_dict = astroid.extract_node(
|
|
"""
|
|
dict.fromkeys({'a':2, 'b': 3, 'c': 3}) #@
|
|
"""
|
|
)
|
|
inferred = next(from_dict.infer())
|
|
assert isinstance(inferred, astroid.Dict)
|
|
itered = inferred.itered()
|
|
assert all(isinstance(elem, astroid.Const) for elem in itered)
|
|
actual_values = [elem.value for elem in itered]
|
|
assert sorted(actual_values) == ["a", "b", "c"]
|
|
|
|
# from a string
|
|
from_string = astroid.extract_node(
|
|
"""
|
|
dict.fromkeys('abc')
|
|
"""
|
|
)
|
|
inferred = next(from_string.infer())
|
|
assert isinstance(inferred, astroid.Dict)
|
|
itered = inferred.itered()
|
|
assert all(isinstance(elem, astroid.Const) for elem in itered)
|
|
actual_values = [elem.value for elem in itered]
|
|
assert sorted(actual_values) == ["a", "b", "c"]
|
|
|
|
# from bytes
|
|
from_bytes = astroid.extract_node(
|
|
"""
|
|
dict.fromkeys(b'abc')
|
|
"""
|
|
)
|
|
inferred = next(from_bytes.infer())
|
|
assert isinstance(inferred, astroid.Dict)
|
|
itered = inferred.itered()
|
|
assert all(isinstance(elem, astroid.Const) for elem in itered)
|
|
actual_values = [elem.value for elem in itered]
|
|
assert sorted(actual_values) == [97, 98, 99]
|
|
|
|
# From list/set/tuple
|
|
from_others = astroid.extract_node(
|
|
"""
|
|
dict.fromkeys(('a', 'b', 'c')) #@
|
|
dict.fromkeys(['a', 'b', 'c']) #@
|
|
dict.fromkeys({'a', 'b', 'c'}) #@
|
|
"""
|
|
)
|
|
for node in from_others:
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Dict)
|
|
itered = inferred.itered()
|
|
assert all(isinstance(elem, astroid.Const) for elem in itered)
|
|
actual_values = [elem.value for elem in itered]
|
|
assert sorted(actual_values) == ["a", "b", "c"]
|
|
|
|
|
|
class TestFunctoolsPartial:
|
|
def test_invalid_functools_partial_calls(self) -> None:
|
|
ast_nodes = astroid.extract_node(
|
|
"""
|
|
from functools import partial
|
|
from unknown import Unknown
|
|
|
|
def test(a, b, c):
|
|
return a + b + c
|
|
|
|
partial() #@
|
|
partial(test) #@
|
|
partial(func=test) #@
|
|
partial(some_func, a=1) #@
|
|
partial(Unknown, a=1) #@
|
|
partial(2, a=1) #@
|
|
partial(test, unknown=1) #@
|
|
"""
|
|
)
|
|
for node in ast_nodes:
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, (astroid.FunctionDef, astroid.Instance))
|
|
assert inferred.qname() in {
|
|
"functools.partial",
|
|
"functools.partial.newfunc",
|
|
}
|
|
|
|
def test_inferred_partial_function_calls(self) -> None:
|
|
ast_nodes = astroid.extract_node(
|
|
"""
|
|
from functools import partial
|
|
def test(a, b):
|
|
return a + b
|
|
partial(test, 1)(3) #@
|
|
partial(test, b=4)(3) #@
|
|
partial(test, b=4)(a=3) #@
|
|
def other_test(a, b, *, c=1):
|
|
return (a + b) * c
|
|
|
|
partial(other_test, 1, 2)() #@
|
|
partial(other_test, 1, 2)(c=4) #@
|
|
partial(other_test, c=4)(1, 3) #@
|
|
partial(other_test, 4, c=4)(4) #@
|
|
partial(other_test, 4, c=4)(b=5) #@
|
|
test(1, 2) #@
|
|
partial(other_test, 1, 2)(c=3) #@
|
|
partial(test, b=4)(a=3) #@
|
|
"""
|
|
)
|
|
expected_values = [4, 7, 7, 3, 12, 16, 32, 36, 3, 9, 7]
|
|
for node, expected_value in zip(ast_nodes, expected_values):
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Const)
|
|
assert inferred.value == expected_value
|
|
|
|
def test_partial_assignment(self) -> None:
|
|
"""Make sure partials are not assigned to original scope."""
|
|
ast_nodes = astroid.extract_node(
|
|
"""
|
|
from functools import partial
|
|
def test(a, b): #@
|
|
return a + b
|
|
test2 = partial(test, 1)
|
|
test2 #@
|
|
def test3_scope(a):
|
|
test3 = partial(test, a)
|
|
test3 #@
|
|
"""
|
|
)
|
|
func1, func2, func3 = ast_nodes
|
|
assert func1.parent.scope() == func2.parent.scope()
|
|
assert func1.parent.scope() != func3.parent.scope()
|
|
partial_func3 = next(func3.infer())
|
|
# use scope of parent, so that it doesn't just refer to self
|
|
scope = partial_func3.parent.scope()
|
|
assert scope.name == "test3_scope", "parented by closure"
|
|
|
|
def test_partial_does_not_affect_scope(self) -> None:
|
|
"""Make sure partials are not automatically assigned."""
|
|
ast_nodes = astroid.extract_node(
|
|
"""
|
|
from functools import partial
|
|
def test(a, b):
|
|
return a + b
|
|
def scope():
|
|
test2 = partial(test, 1)
|
|
test2 #@
|
|
"""
|
|
)
|
|
test2 = next(ast_nodes.infer())
|
|
mod_scope = test2.root()
|
|
scope = test2.parent.scope()
|
|
assert set(mod_scope) == {"test", "scope", "partial"}
|
|
assert set(scope) == {"test2"}
|
|
|
|
def test_multiple_partial_args(self) -> None:
|
|
"Make sure partials remember locked-in args."
|
|
ast_node = astroid.extract_node(
|
|
"""
|
|
from functools import partial
|
|
def test(a, b, c, d, e=5):
|
|
return a + b + c + d + e
|
|
test1 = partial(test, 1)
|
|
test2 = partial(test1, 2)
|
|
test3 = partial(test2, 3)
|
|
test3(4, e=6) #@
|
|
"""
|
|
)
|
|
expected_args = [1, 2, 3, 4]
|
|
expected_keywords = {"e": 6}
|
|
|
|
call_site = astroid.arguments.CallSite.from_call(ast_node)
|
|
called_func = next(ast_node.func.infer())
|
|
called_args = called_func.filled_args + call_site.positional_arguments
|
|
called_keywords = {**called_func.filled_keywords, **call_site.keyword_arguments}
|
|
assert len(called_args) == len(expected_args)
|
|
assert [arg.value for arg in called_args] == expected_args
|
|
assert len(called_keywords) == len(expected_keywords)
|
|
|
|
for keyword, value in expected_keywords.items():
|
|
assert keyword in called_keywords
|
|
assert called_keywords[keyword].value == value
|
|
|
|
|
|
def test_http_client_brain() -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
from http.client import OK
|
|
OK
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Instance)
|
|
|
|
|
|
@pytest.mark.skipif(not PY37_PLUS, reason="Needs 3.7+")
|
|
def test_http_status_brain() -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
import http
|
|
http.HTTPStatus.CONTINUE.phrase
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
# Cannot infer the exact value but the field is there.
|
|
assert inferred is util.Uninferable
|
|
|
|
node = astroid.extract_node(
|
|
"""
|
|
import http
|
|
http.HTTPStatus(200).phrase
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, astroid.Const)
|
|
|
|
|
|
def test_oserror_model() -> None:
|
|
node = astroid.extract_node(
|
|
"""
|
|
try:
|
|
1/0
|
|
except OSError as exc:
|
|
exc #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
strerror = next(inferred.igetattr("strerror"))
|
|
assert isinstance(strerror, astroid.Const)
|
|
assert strerror.value == ""
|
|
|
|
|
|
@pytest.mark.skipif(not PY37_PLUS, reason="Dynamic module attributes since Python 3.7")
|
|
def test_crypt_brain() -> None:
|
|
module = MANAGER.ast_from_module_name("crypt")
|
|
dynamic_attrs = [
|
|
"METHOD_SHA512",
|
|
"METHOD_SHA256",
|
|
"METHOD_BLOWFISH",
|
|
"METHOD_MD5",
|
|
"METHOD_CRYPT",
|
|
]
|
|
for attr in dynamic_attrs:
|
|
assert attr in module
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"code,expected_class,expected_value",
|
|
[
|
|
("'hey'.encode()", astroid.Const, b""),
|
|
("b'hey'.decode()", astroid.Const, ""),
|
|
("'hey'.encode().decode()", astroid.Const, ""),
|
|
],
|
|
)
|
|
def test_str_and_bytes(code, expected_class, expected_value):
|
|
node = astroid.extract_node(code)
|
|
inferred = next(node.infer())
|
|
assert isinstance(inferred, expected_class)
|
|
assert inferred.value == expected_value
|
|
|
|
|
|
def test_no_recursionerror_on_self_referential_length_check() -> None:
|
|
"""
|
|
Regression test for https://github.com/PyCQA/astroid/issues/777
|
|
|
|
This test should only raise an InferenceError and no RecursionError.
|
|
"""
|
|
with pytest.raises(InferenceError):
|
|
node = astroid.extract_node(
|
|
"""
|
|
class Crash:
|
|
def __len__(self) -> int:
|
|
return len(self)
|
|
len(Crash()) #@
|
|
"""
|
|
)
|
|
assert isinstance(node, nodes.NodeNG)
|
|
node.inferred()
|
|
|
|
|
|
def test_inference_on_outer_referential_length_check() -> None:
|
|
"""
|
|
Regression test for https://github.com/PyCQA/pylint/issues/5244
|
|
See also https://github.com/PyCQA/astroid/pull/1234
|
|
|
|
This test should succeed without any error.
|
|
"""
|
|
node = astroid.extract_node(
|
|
"""
|
|
class A:
|
|
def __len__(self) -> int:
|
|
return 42
|
|
|
|
class Crash:
|
|
def __len__(self) -> int:
|
|
a = A()
|
|
return len(a)
|
|
|
|
len(Crash()) #@
|
|
"""
|
|
)
|
|
inferred = node.inferred()
|
|
assert len(inferred) == 1
|
|
assert isinstance(inferred[0], nodes.Const)
|
|
assert inferred[0].value == 42
|
|
|
|
|
|
def test_no_attributeerror_on_self_referential_length_check() -> None:
|
|
"""
|
|
Regression test for https://github.com/PyCQA/pylint/issues/5244
|
|
See also https://github.com/PyCQA/astroid/pull/1234
|
|
|
|
This test should only raise an InferenceError and no AttributeError.
|
|
"""
|
|
with pytest.raises(InferenceError):
|
|
node = astroid.extract_node(
|
|
"""
|
|
class MyClass:
|
|
def some_func(self):
|
|
return lambda: 42
|
|
|
|
def __len__(self):
|
|
return len(self.some_func())
|
|
|
|
len(MyClass()) #@
|
|
"""
|
|
)
|
|
assert isinstance(node, nodes.NodeNG)
|
|
node.inferred()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|