diff options
author | hippo91 <guillaume.peillex@gmail.com> | 2020-06-20 11:37:37 +0200 |
---|---|---|
committer | hippo91 <guillaume.peillex@gmail.com> | 2020-06-20 11:37:37 +0200 |
commit | 1322e5d7790b7e3aa629a4e86e3b9f9b5846886b (patch) | |
tree | a6041c102f12f5cbb2a9f9f9e57edfb573d4a37f | |
parent | 14de71c7838e4bae329292035f1a225aa5218317 (diff) | |
parent | 92f556842c84a2b3cc33a1638fc625b4f67d0d1f (diff) | |
download | astroid-git-1322e5d7790b7e3aa629a4e86e3b9f9b5846886b.tar.gz |
Merge branch 'master' into sum_and_multiply
150 files changed, 1959 insertions, 1599 deletions
diff --git a/.travis.yml b/.travis.yml index 49b37e3d..638720d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,25 +11,21 @@ jobs: env: TOXENV=formatting - python: 3.5 env: TOXENV=py35 - - python: pypy3.5 + - python: pypy3 env: TOXENV=pypy - python: 3.6 env: TOXENV=py36 - python: 3.7 env: TOXENV=py37 - dist: xenial - sudo: true - - python: 3.8-dev + - python: 3.8 env: TOXENV=py38 - dist: xenial - sudo: true before_install: - python --version - uname -a - lsb_release -a install: - python -m pip install pip -U -- python -m pip install tox coverage coveralls +- python -m pip install tox "coverage<5" coveralls - python -m virtualenv --version - python -m easy_install --version - python -m pip --version @@ -2,15 +2,171 @@ astroid's ChangeLog =================== -What's New in astroid 2.4.0? -============================ -Release Date: TBA - * The ``context.path`` is now a ``dict`` and the ``context.push`` method returns ``True`` if the node has been visited a certain amount of times. Close #669 +What's New in astroid 2.5.0? +============================ +Release Date: TBA + +* Added a brain for ``sqlalchemy.orm.session`` + +* Added missing methods to the brain for ``mechanize``, to fix pylint false positives + + Close #793 + +* Added more supported parameters to ``subprocess.check_output`` + +* Added exception inference for `UnicodeDecodeError` + + Close PyCQA/pylint#3639 + +* `FunctionDef.is_generator` properly handles `yield` nodes in `If` tests + + Close PyCQA/pylint#3583 + + +What's New in astroid 2.4.3? +============================ +Release Date: TBA + +* Fix a crash caused by a lookup of a monkey-patched method + + Close PyCQA/pylint#3686 + + +What's New in astroid 2.4.2? +============================ +Release Date: 2020-06-08 + +* `FunctionDef.is_generator` properly handles `yield` nodes in `While` tests + + Close PyCQA/pylint#3519 + +* Properly construct the arguments of infered property descriptors + + Close PyCQA/pylint#3648 + + +What's New in astroid 2.4.1? +============================ +Release Date: 2020-05-05 + +* Handle the case where the raw builder fails to retrieve the ``__all__`` attribute + + Close #772 + +* Restructure the AST parsing heuristic to always pick the same module + + Close PyCQA/pylint#3540 + Close #773 + +* Changed setup.py to work with [distlib](https://pypi.org/project/distlib) + + Close #779 + +* Do not crash with SyntaxError when parsing namedtuples with invalid label + + Close PyCQA/pylint#3549 + +* Protect against ``infer_call_result`` failing with `InferenceError` in `Super.getattr()` + + Close PyCQA/pylint#3529 + + +What's New in astroid 2.4.0? +============================ +Release Date: 2020-04-27 + +* Expose a ast_from_string method in AstroidManager, which will accept + source code as a string and return the corresponding astroid object + + Closes PyCQA/astroid#725 + +* ``BoundMethod.implicit_parameters`` returns a proper value for ``__new__`` + + Close PyCQA/pylint#2335 + +* Allow slots added dynamically to a class to still be inferred + + Close PyCQA/pylint#2334 + +* Allow `FunctionDef.getattr` to look into both instance attrs and special attributes + + Close PyCQA/pylint#1078 + +* Infer qualified ``classmethod`` as a classmethod. + + Close PyCQA/pylint#3417 + +* Prevent a recursion error to happen when inferring the declared metaclass of a class + + Close #749 + +* Raise ``AttributeInferenceError`` when ``getattr()`` receives an empty name + + Close PyCQA/pylint#2991 + +* Prevent a recursion error for self reference variables and `type()` calls. + + Close #199 + +* Do not infer the first argument of a staticmethod in a metaclass as the class itself + + Close PyCQA/pylint#3032 + +* ``NodeNG.bool_value()`` gained an optional ``context`` parameter + + We need to pass an inference context downstream when inferring the boolean + value of a node in order to prevent recursion errors and double inference. + + This fix prevents a recursion error with dask library. + + Close PyCQA/pylint#2985 + +* Pass a context argument to ``astroid.Arguments`` to prevent recursion errors + + Close PyCQA/pylint#3414 + +* Better inference of class and static methods decorated with custom methods + + Close PyCQA/pylint#3209 + +* Reverse the order of decorators for `infer_subscript` + + `path_wrapper` needs to come first, followed by `raise_if_nothing_inferred`, + otherwise we won't handle `StopIteration` correctly. + + Close #762 + +* Prevent a recursion error when inferring self-referential variables without definition + + Close PyCQA/pylint#1285 + +* Numpy `datetime64.astype` return value is inferred as a `ndarray`. + + Close PyCQA/pylint#3332 + +* Skip non ``Assign`` and ``AnnAssign`` nodes from enum reinterpretation + + Closes PyCQA/pylint#3365 + +* Numpy ``ndarray`` attributes ``imag`` and ``real`` are now inferred as ``ndarray``. + + Close PyCQA/pylint#3322 + +* Added a call to ``register_transform`` for all functions of the ``brain_numpy_core_multiarray`` + module in case the current node is an instance of ``astroid.Name`` + + Close #666 + +* Use the parent of the node when inferring aug assign nodes instead of the statement + + Close PyCQA/pylint#2911 + Close PyCQA/pylint#3214 + * Added some functions to the ``brain_numpy_core_umath`` module Close PyCQA/pylint#3319 @@ -100,6 +256,18 @@ Release Date: TBA Close PyCQA/pylint#3274 +* Can access per argument type comments for positional only and keyword only arguments. + + The comments are accessed through through the new + ``Arguments.type_comment_posonlyargs`` and + ``Arguments.type_comment_kwonlyargs`` attributes respectively. + +* Relax upper bound on `wrapt` + + Close #755 + +* Properly analyze CFFI compiled extensions. + What's New in astroid 2.3.2? ============================ Release Date: TBA diff --git a/MANIFEST.in b/MANIFEST.in index 23286ffc..8cf73ba0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,3 +5,5 @@ include COPYING.LESSER include pytest.ini recursive-include tests *.py *.zip *.egg *.pth recursive-include astroid/brain *.py +graft tests +recursive-exclude tests *.pyc @@ -79,7 +79,8 @@ Python Versions --------------- astroid 2.0 is currently available for Python 3 only. If you want Python 2 -support, older versions of astroid will still supported until 2020. +support, use an older version of astroid (though note that these versions +are no longer supported). Test ---- diff --git a/astroid/__init__.py b/astroid/__init__.py index 9feac683..e869274e 100644 --- a/astroid/__init__.py +++ b/astroid/__init__.py @@ -6,6 +6,7 @@ # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com> # Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Nick Drozd <nicholasdrozd@gmail.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/master/COPYING.LESSER diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index 4647a6eb..aa48c537 100644 --- a/astroid/__pkginfo__.py +++ b/astroid/__pkginfo__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2017 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> @@ -9,22 +9,26 @@ # Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017 Calen Pennington <cale@edx.org> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Uilian Ries <uilianries@gmail.com> +# Copyright (c) 2019 Thomas Hisch <t.hisch@gmail.com> +# Copyright (c) 2020 Michael <michael-k@users.noreply.github.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/master/COPYING.LESSER """astroid packaging information""" -version = "2.4.0" +version = "2.5.0" numversion = tuple(int(elem) for elem in version.split(".") if elem.isdigit()) extras_require = {} install_requires = [ "lazy_object_proxy==1.4.*", "six~=1.12", - "wrapt==1.11.*", + "wrapt~=1.11", 'typed-ast>=1.4.0,<1.5;implementation_name== "cpython" and python_version<"3.8"', ] @@ -46,6 +50,7 @@ classifiers = [ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] diff --git a/astroid/_ast.py b/astroid/_ast.py index 66c5cf25..34b74c5f 100644 --- a/astroid/_ast.py +++ b/astroid/_ast.py @@ -4,10 +4,11 @@ from functools import partial from typing import Optional import sys -_ast_py2 = _ast_py3 = None +import astroid + +_ast_py3 = None try: import typed_ast.ast3 as _ast_py3 - import typed_ast.ast27 as _ast_py2 except ImportError: pass @@ -21,28 +22,30 @@ if PY38: FunctionType = namedtuple("FunctionType", ["argtypes", "returns"]) -def _get_parser_module(parse_python_two=False, type_comments_support=True): - if not type_comments_support: - return ast - - if parse_python_two: - parser_module = _ast_py2 - else: - parser_module = _ast_py3 - return parser_module or ast - - -def _parse(string: str, parse_python_two=False, type_comments=True): - parse_module = _get_parser_module( - parse_python_two=parse_python_two, type_comments_support=type_comments +class ParserModule( + namedtuple( + "ParserModule", + [ + "module", + "unary_op_classes", + "cmp_op_classes", + "bool_op_classes", + "bin_op_classes", + "context_classes", + ], ) - parse_func = parse_module.parse - if parse_module is _ast_py3: - if PY38: - parse_func = partial(parse_func, type_comments=type_comments) - if not parse_python_two: - parse_func = partial(parse_func, feature_version=sys.version_info.minor) - return parse_func(string) +): + def parse(self, string: str, type_comments=True): + if self.module is _ast_py3: + if PY38: + parse_func = partial(self.module.parse, type_comments=type_comments) + else: + parse_func = partial( + self.module.parse, feature_version=sys.version_info.minor + ) + else: + parse_func = self.module.parse + return parse_func(string) def parse_function_type_comment(type_comment: str) -> Optional[FunctionType]: @@ -52,3 +55,77 @@ def parse_function_type_comment(type_comment: str) -> Optional[FunctionType]: func_type = _ast_py3.parse(type_comment, "<type_comment>", "func_type") return FunctionType(argtypes=func_type.argtypes, returns=func_type.returns) + + +def get_parser_module(type_comments=True) -> ParserModule: + if not type_comments: + parser_module = ast + else: + parser_module = _ast_py3 + parser_module = parser_module or ast + + unary_op_classes = _unary_operators_from_module(parser_module) + cmp_op_classes = _compare_operators_from_module(parser_module) + bool_op_classes = _bool_operators_from_module(parser_module) + bin_op_classes = _binary_operators_from_module(parser_module) + context_classes = _contexts_from_module(parser_module) + + return ParserModule( + parser_module, + unary_op_classes, + cmp_op_classes, + bool_op_classes, + bin_op_classes, + context_classes, + ) + + +def _unary_operators_from_module(module): + return {module.UAdd: "+", module.USub: "-", module.Not: "not", module.Invert: "~"} + + +def _binary_operators_from_module(module): + binary_operators = { + module.Add: "+", + module.BitAnd: "&", + module.BitOr: "|", + module.BitXor: "^", + module.Div: "/", + module.FloorDiv: "//", + module.MatMult: "@", + module.Mod: "%", + module.Mult: "*", + module.Pow: "**", + module.Sub: "-", + module.LShift: "<<", + module.RShift: ">>", + } + return binary_operators + + +def _bool_operators_from_module(module): + return {module.And: "and", module.Or: "or"} + + +def _compare_operators_from_module(module): + return { + module.Eq: "==", + module.Gt: ">", + module.GtE: ">=", + module.In: "in", + module.Is: "is", + module.IsNot: "is not", + module.Lt: "<", + module.LtE: "<=", + module.NotEq: "!=", + module.NotIn: "not in", + } + + +def _contexts_from_module(module): + return { + module.Load: astroid.Load, + module.Store: astroid.Store, + module.Del: astroid.Del, + module.Param: astroid.Store, + } diff --git a/astroid/arguments.py b/astroid/arguments.py index 0a853605..5f4d9092 100644 --- a/astroid/arguments.py +++ b/astroid/arguments.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> @@ -20,20 +20,27 @@ class CallSite: It needs a call context, which contains the arguments and the keyword arguments that were passed into a given call site. - In order to infer what an argument represents, call - :meth:`infer_argument` with the corresponding function node - and the argument name. + In order to infer what an argument represents, call :meth:`infer_argument` + with the corresponding function node and the argument name. + + :param callcontext: + An instance of :class:`astroid.context.CallContext`, that holds + the arguments for the call site. + :param argument_context_map: + Additional contexts per node, passed in from :attr:`astroid.context.Context.extra_context` + :param context: + An instance of :class:`astroid.context.Context`. """ - def __init__(self, callcontext, argument_context_map=None): + def __init__(self, callcontext, argument_context_map=None, context=None): if argument_context_map is None: argument_context_map = {} self.argument_context_map = argument_context_map args = callcontext.args keywords = callcontext.keywords self.duplicated_keywords = set() - self._unpacked_args = self._unpack_args(args) - self._unpacked_kwargs = self._unpack_keywords(keywords) + self._unpacked_args = self._unpack_args(args, context=context) + self._unpacked_kwargs = self._unpack_keywords(keywords, context=context) self.positional_arguments = [ arg for arg in self._unpacked_args if arg is not util.Uninferable @@ -45,10 +52,18 @@ class CallSite: } @classmethod - def from_call(cls, call_node): - """Get a CallSite object from the given Call node.""" + def from_call(cls, call_node, context=None): + """Get a CallSite object from the given Call node. + + :param context: + An instance of :class:`astroid.context.Context` that will be used + to force a single inference path. + """ + + # Determine the callcontext from the given `context` object if any. + context = context or contextmod.InferenceContext() callcontext = contextmod.CallContext(call_node.args, call_node.keywords) - return cls(callcontext) + return cls(callcontext, context=context) def has_invalid_arguments(self): """Check if in the current CallSite were passed *invalid* arguments @@ -70,9 +85,9 @@ class CallSite: """ return len(self.keyword_arguments) != len(self._unpacked_kwargs) - def _unpack_keywords(self, keywords): + def _unpack_keywords(self, keywords, context=None): values = {} - context = contextmod.InferenceContext() + context = context or contextmod.InferenceContext() context.extra_context = self.argument_context_map for name, value in keywords: if name is None: @@ -110,9 +125,9 @@ class CallSite: values[name] = value return values - def _unpack_args(self, args): + def _unpack_args(self, args, context=None): values = [] - context = contextmod.InferenceContext() + context = context or contextmod.InferenceContext() context.extra_context = self.argument_context_map for arg in args: if isinstance(arg, nodes.Starred): diff --git a/astroid/as_string.py b/astroid/as_string.py index 57049141..653411f4 100644 --- a/astroid/as_string.py +++ b/astroid/as_string.py @@ -1,15 +1,19 @@ # -*- coding: utf-8 -*- # Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2010 Daniel Harding <dharding@gmail.com> -# Copyright (c) 2013-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2013-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2013-2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> -# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> +# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017 rr- <rr-@sakuya.pl> +# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2019 Alex Hall <alex.mojaki@gmail.com> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER @@ -80,6 +84,15 @@ class AsStringVisitor: ## visit_<node> methods ########################################### + def visit_await(self, node): + return "await %s" % node.value.accept(self) + + def visit_asyncwith(self, node): + return "async %s" % self.visit_with(node) + + def visit_asyncfor(self, node): + return "async %s" % self.visit_for(node) + def visit_arguments(self, node): """return an astroid.Function node as string""" return node.format_args() @@ -180,11 +193,12 @@ class AsStringVisitor: def visit_comprehension(self, node): """return an astroid.Comprehension node as string""" ifs = "".join(" if %s" % n.accept(self) for n in node.ifs) - return "for %s in %s%s" % ( + generated = "for %s in %s%s" % ( node.target.accept(self), node.iter.accept(self), ifs, ) + return "%s%s" % ("async " if node.is_async else "", generated) def visit_const(self, node): """return an astroid.Const node as string""" @@ -248,7 +262,7 @@ class AsStringVisitor: def visit_excepthandler(self, node): if node.type: if node.name: - excs = "except %s, %s" % ( + excs = "except %s as %s" % ( node.type.accept(self), node.name.accept(self), ) @@ -300,6 +314,41 @@ class AsStringVisitor: _import_string(node.names), ) + def visit_joinedstr(self, node): + string = "".join( + # Use repr on the string literal parts + # to get proper escapes, e.g. \n, \\, \" + # But strip the quotes off the ends + # (they will always be one character: ' or ") + repr(value.value)[1:-1] + # Literal braces must be doubled to escape them + .replace("{", "{{").replace("}", "}}") + # Each value in values is either a string literal (Const) + # or a FormattedValue + if type(value).__name__ == "Const" else value.accept(self) + for value in node.values + ) + + # Try to find surrounding quotes that don't appear at all in the string. + # Because the formatted values inside {} can't contain backslash (\) + # using a triple quote is sometimes necessary + for quote in ["'", '"', '"""', "'''"]: + if quote not in string: + break + + return "f" + quote + string + quote + + def visit_formattedvalue(self, node): + result = node.value.accept(self) + if node.conversion and node.conversion >= 0: + # e.g. if node.conversion == 114: result += "!r" + result += "!" + chr(node.conversion) + if node.format_spec: + # The format spec is itself a JoinedString, i.e. an f-string + # We strip the f and quotes of the ends + result += ":" + node.format_spec.accept(self)[2:-1] + return "{%s}" % result + def handle_functiondef(self, node, keyword): """return a (possibly async) function definition node as string""" decorate = node.decorators.accept(self) if node.decorators else "" @@ -401,6 +450,16 @@ class AsStringVisitor: """return an astroid.Name node as string""" return node.name + def visit_namedexpr(self, node): + """Return an assignment expression node as string""" + target = node.target.accept(self) + value = node.value.accept(self) + return "%s := %s" % (target, value) + + def visit_nonlocal(self, node): + """return an astroid.Nonlocal node as string""" + return "nonlocal %s" % ", ".join(node.names) + def visit_pass(self, node): """return an astroid.Pass node as string""" return "pass" @@ -417,14 +476,11 @@ class AsStringVisitor: def visit_raise(self, node): """return an astroid.Raise node as string""" if node.exc: - if node.inst: - if node.tback: - return "raise %s, %s, %s" % ( - node.exc.accept(self), - node.inst.accept(self), - node.tback.accept(self), - ) - return "raise %s, %s" % (node.exc.accept(self), node.inst.accept(self)) + if node.cause: + return "raise %s from %s" % ( + node.exc.accept(self), + node.cause.accept(self), + ) return "raise %s" % node.exc.accept(self) return "raise" @@ -529,6 +585,15 @@ class AsStringVisitor: return "(%s)" % (expr,) + def visit_yieldfrom(self, node): + """ Return an astroid.YieldFrom node as string. """ + yi_val = (" " + node.value.accept(self)) if node.value else "" + expr = "yield from" + yi_val + if node.parent.is_statement: + return expr + + return "(%s)" % (expr,) + def visit_starred(self, node): """return Starred node as string""" return "*" + node.value.accept(self) @@ -544,103 +609,11 @@ class AsStringVisitor: def visit_uninferable(self, node): return str(node) + def visit_property(self, node): + return node.function.accept(self) -class AsStringVisitor3(AsStringVisitor): - """AsStringVisitor3 overwrites some AsStringVisitor methods""" - - def visit_excepthandler(self, node): - if node.type: - if node.name: - excs = "except %s as %s" % ( - node.type.accept(self), - node.name.accept(self), - ) - else: - excs = "except %s" % node.type.accept(self) - else: - excs = "except" - return "%s:\n%s" % (excs, self._stmt_list(node.body)) - - def visit_nonlocal(self, node): - """return an astroid.Nonlocal node as string""" - return "nonlocal %s" % ", ".join(node.names) - - def visit_raise(self, node): - """return an astroid.Raise node as string""" - if node.exc: - if node.cause: - return "raise %s from %s" % ( - node.exc.accept(self), - node.cause.accept(self), - ) - return "raise %s" % node.exc.accept(self) - return "raise" - - def visit_yieldfrom(self, node): - """ Return an astroid.YieldFrom node as string. """ - yi_val = (" " + node.value.accept(self)) if node.value else "" - expr = "yield from" + yi_val - if node.parent.is_statement: - return expr - - return "(%s)" % (expr,) - - def visit_await(self, node): - return "await %s" % node.value.accept(self) - - def visit_asyncwith(self, node): - return "async %s" % self.visit_with(node) - - def visit_asyncfor(self, node): - return "async %s" % self.visit_for(node) - - def visit_joinedstr(self, node): - string = "".join( - # Use repr on the string literal parts - # to get proper escapes, e.g. \n, \\, \" - # But strip the quotes off the ends - # (they will always be one character: ' or ") - repr(value.value)[1:-1] - # Literal braces must be doubled to escape them - .replace("{", "{{").replace("}", "}}") - # Each value in values is either a string literal (Const) - # or a FormattedValue - if type(value).__name__ == "Const" else value.accept(self) - for value in node.values - ) - - # Try to find surrounding quotes that don't appear at all in the string. - # Because the formatted values inside {} can't contain backslash (\) - # using a triple quote is sometimes necessary - for quote in ["'", '"', '"""', "'''"]: - if quote not in string: - break - - return "f" + quote + string + quote - - def visit_formattedvalue(self, node): - result = node.value.accept(self) - if node.conversion and node.conversion >= 0: - # e.g. if node.conversion == 114: result += "!r" - result += "!" + chr(node.conversion) - if node.format_spec: - # The format spec is itself a JoinedString, i.e. an f-string - # We strip the f and quotes of the ends - result += ":" + node.format_spec.accept(self)[2:-1] - return "{%s}" % result - - def visit_comprehension(self, node): - """return an astroid.Comprehension node as string""" - return "%s%s" % ( - "async " if node.is_async else "", - super(AsStringVisitor3, self).visit_comprehension(node), - ) - - def visit_namedexpr(self, node): - """Return an assignment expression node as string""" - target = node.target.accept(self) - value = node.value.accept(self) - return "%s := %s" % (target, value) + def visit_evaluatedobject(self, node): + return node.original.accept(self) def _import_string(names): @@ -654,7 +627,5 @@ def _import_string(names): return ", ".join(_names) -AsStringVisitor = AsStringVisitor3 - # This sets the default indent to 4 spaces. to_code = AsStringVisitor(" ") diff --git a/astroid/bases.py b/astroid/bases.py index 0c6cb9b2..9c743031 100644 --- a/astroid/bases.py +++ b/astroid/bases.py @@ -1,15 +1,19 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> # Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com> # Copyright (c) 2017 Calen Pennington <calen.pennington@gmail.com> +# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> # Copyright (c) 2018 Daniel Colascione <dancol@dancol.org> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER @@ -288,7 +292,7 @@ class Instance(BaseInstance): def display_type(self): return "Instance of" - def bool_value(self): + def bool_value(self, context=None): """Infer the truth value for an Instance The truth value of an instance is determined by these conditions: @@ -301,7 +305,7 @@ class Instance(BaseInstance): nonzero. If a class defines neither __len__() nor __bool__(), all its instances are considered true. """ - context = contextmod.InferenceContext() + context = context or contextmod.InferenceContext() context.callcontext = contextmod.CallContext(args=[]) context.boundnode = self @@ -376,7 +380,7 @@ class UnboundMethod(Proxy): return (Instance(x) if x is not util.Uninferable else x for x in infer) return self._proxied.infer_call_result(caller, context) - def bool_value(self): + def bool_value(self, context=None): return True @@ -391,6 +395,9 @@ class BoundMethod(UnboundMethod): self.bound = bound def implicit_parameters(self): + if self.name == "__new__": + # __new__ acts as a classmethod but the class argument is not implicit. + return 0 return 1 def is_bound(self): @@ -479,9 +486,9 @@ class BoundMethod(UnboundMethod): if new_cls: return iter((new_cls,)) - return super(BoundMethod, self).infer_call_result(caller, context) + return super().infer_call_result(caller, context) - def bool_value(self): + def bool_value(self, context=None): return True @@ -507,7 +514,7 @@ class Generator(BaseInstance): def display_type(self): return "Generator" - def bool_value(self): + def bool_value(self, context=None): return True def __repr__(self): diff --git a/astroid/brain/brain_argparse.py b/astroid/brain/brain_argparse.py index d4899117..6a7556f6 100644 --- a/astroid/brain/brain_argparse.py +++ b/astroid/brain/brain_argparse.py @@ -2,7 +2,7 @@ from astroid import MANAGER, arguments, nodes, inference_tip, UseInferenceDefaul def infer_namespace(node, context=None): - callsite = arguments.CallSite.from_call(node) + callsite = arguments.CallSite.from_call(node, context=context) if not callsite.keyword_arguments: # Cannot make sense of it. raise UseInferenceDefault() diff --git a/astroid/brain/brain_boto3.py b/astroid/brain/brain_boto3.py new file mode 100644 index 00000000..342ca571 --- /dev/null +++ b/astroid/brain/brain_boto3.py @@ -0,0 +1,28 @@ +# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html +# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER + +"""Astroid hooks for understanding boto3.ServiceRequest()""" +import astroid +from astroid import MANAGER, extract_node + +BOTO_SERVICE_FACTORY_QUALIFIED_NAME = "boto3.resources.base.ServiceResource" + + +def service_request_transform(node): + """Transform ServiceResource to look like dynamic classes""" + code = """ + def __getattr__(self, attr): + return 0 + """ + func_getattr = extract_node(code) + node.locals["__getattr__"] = [func_getattr] + return node + + +def _looks_like_boto3_service_request(node): + return node.qname() == BOTO_SERVICE_FACTORY_QUALIFIED_NAME + + +MANAGER.register_transform( + astroid.ClassDef, service_request_transform, _looks_like_boto3_service_request +) diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index 3cbe48f7..4b07ac5c 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -1,8 +1,14 @@ -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# -*- coding: utf-8 -*- +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Rene Zhang <rz99@cornell.edu> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> +# Copyright (c) 2019 Stanislav Levin <slev@altlinux.org> +# Copyright (c) 2019 David Liu <david@cs.toronto.edu> +# Copyright (c) 2019 Bryce Guinta <bryce.guinta@protonmail.com> +# Copyright (c) 2019 Frédéric Chapoton <fchapoton2@gmail.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/master/COPYING.LESSER @@ -177,9 +183,14 @@ def _container_generic_transform(arg, context, klass, iterables, build_elts): elts = [elt.value for elt in arg.elts] else: # TODO: Does not handle deduplication for sets. - elts = filter( - None, map(partial(helpers.safe_infer, context=context), arg.elts) - ) + elts = [] + for element in arg.elts: + inferred = helpers.safe_infer(element, context=context) + if inferred: + evaluated_object = nodes.EvaluatedObject( + original=element, value=inferred + ) + elts.append(evaluated_object) elif isinstance(arg, nodes.Dict): # Dicts need to have consts as strings already. if not all(isinstance(elt[0], nodes.Const) for elt in arg.items): @@ -293,7 +304,7 @@ def infer_dict(node, context=None): If a case can't be inferred, we'll fallback to default inference. """ - call = arguments.CallSite.from_call(node) + call = arguments.CallSite.from_call(node, context=context) if call.has_invalid_arguments() or call.has_invalid_keywords(): raise UseInferenceDefault @@ -527,7 +538,7 @@ def infer_bool(node, context=None): if inferred is util.Uninferable: return util.Uninferable - bool_value = inferred.bool_value() + bool_value = inferred.bool_value(context=context) if bool_value is util.Uninferable: return util.Uninferable return nodes.Const(bool_value) @@ -597,7 +608,7 @@ def infer_issubclass(callnode, context=None): :rtype nodes.Const: Boolean Const value of the `issubclass` call :raises UseInferenceDefault: If the node cannot be inferred """ - call = arguments.CallSite.from_call(callnode) + call = arguments.CallSite.from_call(callnode, context=context) if call.keyword_arguments: # issubclass doesn't support keyword arguments raise UseInferenceDefault("TypeError: issubclass() takes no keyword arguments") @@ -644,7 +655,7 @@ def infer_isinstance(callnode, context=None): :raises UseInferenceDefault: If the node cannot be inferred """ - call = arguments.CallSite.from_call(callnode) + call = arguments.CallSite.from_call(callnode, context=context) if call.keyword_arguments: # isinstance doesn't support keyword arguments raise UseInferenceDefault("TypeError: isinstance() takes no keyword arguments") @@ -701,7 +712,7 @@ def infer_len(node, context=None): :param context.InferenceContext: node context :rtype nodes.Const: a Const node with the inferred length, if possible """ - call = arguments.CallSite.from_call(node) + call = arguments.CallSite.from_call(node, context=context) if call.keyword_arguments: raise UseInferenceDefault("TypeError: len() must take no keyword arguments") if len(call.positional_arguments) != 1: @@ -723,7 +734,7 @@ def infer_str(node, context=None): :param context.InferenceContext: node context :rtype nodes.Const: a Const containing an empty string """ - call = arguments.CallSite.from_call(node) + call = arguments.CallSite.from_call(node, context=context) if call.keyword_arguments: raise UseInferenceDefault("TypeError: str() must take no keyword arguments") try: @@ -739,7 +750,7 @@ def infer_int(node, context=None): :param context.InferenceContext: node context :rtype nodes.Const: a Const containing the integer value of the int() call """ - call = arguments.CallSite.from_call(node) + call = arguments.CallSite.from_call(node, context=context) if call.keyword_arguments: raise UseInferenceDefault("TypeError: int() must take no keyword arguments") @@ -782,7 +793,7 @@ def infer_dict_fromkeys(node, context=None): new_node.postinit(elements) return new_node - call = arguments.CallSite.from_call(node) + call = arguments.CallSite.from_call(node, context=context) if call.keyword_arguments: raise UseInferenceDefault("TypeError: int() must take no keyword arguments") if len(call.positional_arguments) not in {1, 2}: diff --git a/astroid/brain/brain_collections.py b/astroid/brain/brain_collections.py index e5b09ec8..669c6ca4 100644 --- a/astroid/brain/brain_collections.py +++ b/astroid/brain/brain_collections.py @@ -3,6 +3,7 @@ # Copyright (c) 2016-2017 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com> # Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_dateutil.py b/astroid/brain/brain_dateutil.py index a1c270fe..9fdb9fde 100644 --- a/astroid/brain/brain_dateutil.py +++ b/astroid/brain/brain_dateutil.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015 raylu <lurayl@gmail.com> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> diff --git a/astroid/brain/brain_fstrings.py b/astroid/brain/brain_fstrings.py index 7d8c7b68..298d58af 100644 --- a/astroid/brain/brain_fstrings.py +++ b/astroid/brain/brain_fstrings.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_functools.py b/astroid/brain/brain_functools.py index 8b594efe..d6c60691 100644 --- a/astroid/brain/brain_functools.py +++ b/astroid/brain/brain_functools.py @@ -1,4 +1,5 @@ -# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2018 hippo91 <guillaume.peillex@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> """Astroid hooks for understanding functools library module.""" @@ -62,7 +63,7 @@ def _transform_lru_cache(node, context=None): def _functools_partial_inference(node, context=None): - call = arguments.CallSite.from_call(node) + call = arguments.CallSite.from_call(node, context=context) number_of_positional = len(call.positional_arguments) if number_of_positional < 1: raise astroid.UseInferenceDefault( diff --git a/astroid/brain/brain_gi.py b/astroid/brain/brain_gi.py index d8e47d22..e49f3a22 100644 --- a/astroid/brain/brain_gi.py +++ b/astroid/brain/brain_gi.py @@ -1,11 +1,14 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Cole Robinson <crobinso@redhat.com> -# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 David Shea <dshea@redhat.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2016 Giuseppe Scrivano <gscrivan@redhat.com> +# Copyright (c) 2018 Christoph Reiter <reiter.christoph@gmail.com> +# Copyright (c) 2019 Philipp Hörist <philipp@hoerist.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_hashlib.py b/astroid/brain/brain_hashlib.py index 98ae774d..eb34e159 100644 --- a/astroid/brain/brain_hashlib.py +++ b/astroid/brain/brain_hashlib.py @@ -1,4 +1,6 @@ # Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2018 David Poirier <david-poirier-csn@users.noreply.github.com> +# Copyright (c) 2018 wgehalo <wgehalo@gmail.com> # Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html diff --git a/astroid/brain/brain_http.py b/astroid/brain/brain_http.py index 6d7fb7a5..b16464e8 100644 --- a/astroid/brain/brain_http.py +++ b/astroid/brain/brain_http.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2018-2019 Claudiu Popa <pcmanticore@gmail.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_io.py b/astroid/brain/brain_io.py index 4c689225..c7453112 100644 --- a/astroid/brain/brain_io.py +++ b/astroid/brain/brain_io.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_mechanize.py b/astroid/brain/brain_mechanize.py index 93f282ec..88623a82 100644 --- a/astroid/brain/brain_mechanize.py +++ b/astroid/brain/brain_mechanize.py @@ -1,6 +1,6 @@ # Copyright (c) 2012-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html @@ -15,13 +15,70 @@ def mechanize_transform(): """ class Browser(object): + def __getattr__(self, name): + return None + def __getitem__(self, name): + return None + def __setitem__(self, name, val): + return None + def back(self, n=1): + return None + def clear_history(self): + return None + def click(self, *args, **kwds): + return None + def click_link(self, link=None, **kwds): + return None + def close(self): + return None + def encoding(self): + return None + def find_link(self, text=None, text_regex=None, name=None, name_regex=None, url=None, url_regex=None, tag=None, predicate=None, nr=0): + return None + def follow_link(self, link=None, **kwds): + return None + def forms(self): + return None + def geturl(self): + return None + def global_form(self): + return None + def links(self, **kwds): + return None + def open_local_file(self, filename): + return None def open(self, url, data=None, timeout=None): return None def open_novisit(self, url, data=None, timeout=None): return None def open_local_file(self, filename): return None - + def reload(self): + return None + def response(self): + return None + def select_form(self, name=None, predicate=None, nr=None, **attrs): + return None + def set_cookie(self, cookie_string): + return None + def set_handle_referer(self, handle): + return None + def set_header(self, header, value=None): + return None + def set_html(self, html, url="http://example.com/"): + return None + def set_response(self, response): + return None + def set_simple_cookie(self, name, value, domain, path='/'): + return None + def submit(self, *args, **kwds): + return None + def title(self): + return None + def viewing_html(self): + return None + def visit_response(self, response, request=None): + return None """ ) diff --git a/astroid/brain/brain_multiprocessing.py b/astroid/brain/brain_multiprocessing.py index 71256ee3..3629b032 100644 --- a/astroid/brain/brain_multiprocessing.py +++ b/astroid/brain/brain_multiprocessing.py @@ -1,4 +1,5 @@ -# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py index de240671..13fcf793 100644 --- a/astroid/brain/brain_namedtuple_enum.py +++ b/astroid/brain/brain_namedtuple_enum.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2012-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru> @@ -11,6 +11,8 @@ # Copyright (c) 2016 Mateusz Bysiek <mb@mbdev.pl> # Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -121,6 +123,8 @@ def infer_func_form(node, base_type, context=None, enum=False): except (AttributeError, exceptions.InferenceError): raise UseInferenceDefault() + attributes = [attr for attr in attributes if " " not in attr] + # If we can't infer the name of the class, don't crash, up to this point # we know it is a namedtuple anyway. name = name or "Uninferable" @@ -167,7 +171,7 @@ def infer_named_tuple(node, context=None): class_node, name, attributes = infer_func_form( node, tuple_base_name, context=context ) - call_site = arguments.CallSite.from_call(node) + call_site = arguments.CallSite.from_call(node, context=context) func = next(extract_node("import collections; collections.namedtuple").infer()) try: rename = next(call_site.infer_argument(func, "rename", context)).bool_value() @@ -317,6 +321,8 @@ def infer_enum_class(node): targets = stmt.targets elif isinstance(stmt, nodes.AnnAssign): targets = [stmt.target] + else: + continue inferred_return_value = None if isinstance(stmt, nodes.Assign): diff --git a/astroid/brain/brain_nose.py b/astroid/brain/brain_nose.py index 7b12d760..d0280a3f 100644 --- a/astroid/brain/brain_nose.py +++ b/astroid/brain/brain_nose.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html diff --git a/astroid/brain/brain_numpy_core_fromnumeric.py b/astroid/brain/brain_numpy_core_fromnumeric.py index 43b30e4d..62dfe991 100644 --- a/astroid/brain/brain_numpy_core_fromnumeric.py +++ b/astroid/brain/brain_numpy_core_fromnumeric.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_numpy_core_function_base.py b/astroid/brain/brain_numpy_core_function_base.py index 05a73d96..58aa0a98 100644 --- a/astroid/brain/brain_numpy_core_function_base.py +++ b/astroid/brain/brain_numpy_core_function_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_numpy_core_multiarray.py b/astroid/brain/brain_numpy_core_multiarray.py index 18218436..b2e32bc8 100644 --- a/astroid/brain/brain_numpy_core_multiarray.py +++ b/astroid/brain/brain_numpy_core_multiarray.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019-2020 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER @@ -85,3 +85,8 @@ for method_name, function_src in METHODS_TO_BE_INFERRED.items(): astroid.inference_tip(inference_function), functools.partial(looks_like_numpy_member, method_name), ) + astroid.MANAGER.register_transform( + astroid.Name, + astroid.inference_tip(inference_function), + functools.partial(looks_like_numpy_member, method_name), + ) diff --git a/astroid/brain/brain_numpy_core_numeric.py b/astroid/brain/brain_numpy_core_numeric.py index ba43c941..2a6f37e3 100644 --- a/astroid/brain/brain_numpy_core_numeric.py +++ b/astroid/brain/brain_numpy_core_numeric.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_numpy_core_numerictypes.py b/astroid/brain/brain_numpy_core_numerictypes.py index 42021fae..6ac4a146 100644 --- a/astroid/brain/brain_numpy_core_numerictypes.py +++ b/astroid/brain/brain_numpy_core_numerictypes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019-2020 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER @@ -11,6 +11,10 @@ import astroid def numpy_core_numerictypes_transform(): + # TODO: Uniformize the generic API with the ndarray one. + # According to numpy doc the generic object should expose + # the same API than ndarray. This has been done here partially + # through the astype method. return astroid.parse( """ # different types defined in numerictypes.py @@ -35,7 +39,7 @@ def numpy_core_numerictypes_transform(): def argmax(self): return uninferable def argmin(self): return uninferable def argsort(self): return uninferable - def astype(self): return uninferable + def astype(self, dtype, order='K', casting='unsafe', subok=True, copy=True): return np.ndarray([0, 0]) def base(self): return uninferable def byteswap(self): return uninferable def choose(self): return uninferable diff --git a/astroid/brain/brain_numpy_core_umath.py b/astroid/brain/brain_numpy_core_umath.py index 69a3a90d..d76161d0 100644 --- a/astroid/brain/brain_numpy_core_umath.py +++ b/astroid/brain/brain_numpy_core_umath.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_numpy_ndarray.py b/astroid/brain/brain_numpy_ndarray.py index 302ee4a4..d40a7dd0 100644 --- a/astroid/brain/brain_numpy_ndarray.py +++ b/astroid/brain/brain_numpy_ndarray.py @@ -1,6 +1,6 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2017-2020 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER @@ -24,20 +24,20 @@ def infer_numpy_ndarray(node, context=None): self.dtype = None self.flags = None self.flat = None - self.imag = None + self.imag = np.ndarray([0, 0]) self.itemsize = None self.nbytes = None self.ndim = None - self.real = None + self.real = np.ndarray([0, 0]) self.shape = numpy.ndarray([0, 0]) self.size = None self.strides = None def __abs__(self): return numpy.ndarray([0, 0]) - def __add__(self, value): return numpy.ndarray([0, 0]) - def __and__(self, value): return numpy.ndarray([0, 0]) - def __array__(self, dtype=None): return numpy.ndarray([0, 0]) - def __array_wrap__(self, obj): return numpy.ndarray([0, 0]) + def __add__(self, value): return numpy.ndarray([0, 0]) + def __and__(self, value): return numpy.ndarray([0, 0]) + def __array__(self, dtype=None): return numpy.ndarray([0, 0]) + def __array_wrap__(self, obj): return numpy.ndarray([0, 0]) def __contains__(self, key): return True def __copy__(self): return numpy.ndarray([0, 0]) def __deepcopy__(self, memo): return numpy.ndarray([0, 0]) diff --git a/astroid/brain/brain_numpy_random_mtrand.py b/astroid/brain/brain_numpy_random_mtrand.py index 772bfc4e..cffdceef 100644 --- a/astroid/brain/brain_numpy_random_mtrand.py +++ b/astroid/brain/brain_numpy_random_mtrand.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER @@ -40,7 +40,7 @@ def numpy_random_mtrand_transform(): def poisson(lam=1.0, size=None): return uninferable def power(a, size=None): return uninferable def rand(*args): return uninferable - def randint(low, high=None, size=None, dtype='l'): + def randint(low, high=None, size=None, dtype='l'): import numpy return numpy.ndarray((1,1)) def randn(*args): return uninferable diff --git a/astroid/brain/brain_numpy_utils.py b/astroid/brain/brain_numpy_utils.py index 2bad01ee..b29d2719 100644 --- a/astroid/brain/brain_numpy_utils.py +++ b/astroid/brain/brain_numpy_utils.py @@ -1,4 +1,5 @@ -# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019-2020 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 Claudiu Popa <pcmanticore@gmail.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/master/COPYING.LESSER @@ -48,9 +49,17 @@ def looks_like_numpy_member( :param node: node to test :return: True if the node is a member of numpy """ - return ( + if ( isinstance(node, astroid.Attribute) and node.attrname == member_name and isinstance(node.expr, astroid.Name) and _is_a_numpy_module(node.expr) - ) + ): + return True + if ( + isinstance(node, astroid.Name) + and node.name == member_name + and node.root().name.startswith("numpy") + ): + return True + return False diff --git a/astroid/brain/brain_pytest.py b/astroid/brain/brain_pytest.py index d7e3ac8a..56202ab8 100644 --- a/astroid/brain/brain_pytest.py +++ b/astroid/brain/brain_pytest.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Jeff Quast <contact@jeffquast.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2016 Florian Bruhin <me@the-compiler.org> diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py index 8679d140..b703b373 100644 --- a/astroid/brain/brain_qt.py +++ b/astroid/brain/brain_qt.py @@ -1,7 +1,8 @@ -# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2017 Roy Wright <roy@wright.org> # Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Antoine Boellinger <aboellinger@hotmail.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_scipy_signal.py b/astroid/brain/brain_scipy_signal.py index 3da59737..996300d4 100755 --- a/astroid/brain/brain_scipy_signal.py +++ b/astroid/brain/brain_scipy_signal.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019 European Synchrotron Radiation Facility
+# Copyright (c) 2019 Valentin Valls <valentin.valls@esrf.fr> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
diff --git a/astroid/brain/brain_six.py b/astroid/brain/brain_six.py index b342fbf5..46d9fa32 100644 --- a/astroid/brain/brain_six.py +++ b/astroid/brain/brain_six.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> @@ -50,7 +50,8 @@ input = input from sys import intern map = map range = range -from imp import reload as reload_module +from importlib import reload +reload_module = lambda module: reload(module) from functools import reduce from shlex import quote as shlex_quote from io import StringIO diff --git a/astroid/brain/brain_sqlalchemy.py b/astroid/brain/brain_sqlalchemy.py new file mode 100644 index 00000000..c80eee1e --- /dev/null +++ b/astroid/brain/brain_sqlalchemy.py @@ -0,0 +1,35 @@ +import astroid + + +def _session_transform(): + return astroid.parse( + """ + from sqlalchemy.orm.session import Session + + class sessionmaker: + def __init__( + self, + bind=None, + class_=Session, + autoflush=True, + autocommit=False, + expire_on_commit=True, + info=None, + **kw + ): + return + + def __call__(self, **local_kw): + return Session() + + def configure(self, **new_kw): + return + + return Session() + """ + ) + + +astroid.register_module_extender( + astroid.MANAGER, "sqlalchemy.orm.session", _session_transform +) diff --git a/astroid/brain/brain_ssl.py b/astroid/brain/brain_ssl.py index 893d8a2f..2ae21c35 100644 --- a/astroid/brain/brain_ssl.py +++ b/astroid/brain/brain_ssl.py @@ -1,5 +1,6 @@ -# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> +# Copyright (c) 2019 Benjamin Elven <25181435+S3ntinelX@users.noreply.github.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/master/COPYING.LESSER diff --git a/astroid/brain/brain_subprocess.py b/astroid/brain/brain_subprocess.py index 72f4b461..2769a039 100644 --- a/astroid/brain/brain_subprocess.py +++ b/astroid/brain/brain_subprocess.py @@ -1,6 +1,8 @@ -# Copyright (c) 2016-2017 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com> +# Copyright (c) 2018 Peter Talley <peterctalley@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER @@ -74,6 +76,12 @@ def _subprocess_transform(): restore_signals=True, preexec_fn=None, pass_fds=(), + input=None, + bufsize=0, + executable=None, + close_fds=False, + startupinfo=None, + creationflags=0, start_new_session=False ): """.strip() @@ -93,6 +101,12 @@ def _subprocess_transform(): restore_signals=True, preexec_fn=None, pass_fds=(), + input=None, + bufsize=0, + executable=None, + close_fds=False, + startupinfo=None, + creationflags=0, start_new_session=False ): """.strip() diff --git a/astroid/brain/brain_threading.py b/astroid/brain/brain_threading.py index db32ae79..ba3085b5 100644 --- a/astroid/brain/brain_threading.py +++ b/astroid/brain/brain_threading.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html diff --git a/astroid/brain/brain_uuid.py b/astroid/brain/brain_uuid.py index 8bda631d..5a33fc25 100644 --- a/astroid/brain/brain_uuid.py +++ b/astroid/brain/brain_uuid.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.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/master/COPYING.LESSER diff --git a/astroid/builder.py b/astroid/builder.py index 7f30aaeb..142764b1 100644 --- a/astroid/builder.py +++ b/astroid/builder.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2013 Phil Schaf <flying-sheep@web.de> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014-2015 Google, Inc. # Copyright (c) 2014 Alexander Presnyakov <flagist0@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> @@ -22,7 +22,7 @@ import os import textwrap from tokenize import detect_encoding -from astroid._ast import _parse +from astroid._ast import get_parser_module from astroid import bases from astroid import exceptions from astroid import manager @@ -42,7 +42,7 @@ _TRANSIENT_FUNCTION = "__" # The comment used to select a statement to be extracted # when calling extract_node. _STATEMENT_SELECTOR = "#@" - +MISPLACED_TYPE_ANNOTATION_ERROR = "misplaced type annotation" MANAGER = manager.AstroidManager() @@ -77,7 +77,7 @@ class AstroidBuilder(raw_building.InspectBuilder): # pylint: disable=redefined-outer-name def __init__(self, manager=None, apply_transforms=True): - super(AstroidBuilder, self).__init__() + super().__init__() self._manager = manager or MANAGER self._apply_transforms = apply_transforms @@ -165,7 +165,7 @@ class AstroidBuilder(raw_building.InspectBuilder): def _data_build(self, data, modname, path): """Build tree node from data and add some informations""" try: - node = _parse_string(data) + node, parser_module = _parse_string(data, type_comments=True) except (TypeError, ValueError, SyntaxError) as exc: raise exceptions.AstroidSyntaxError( "Parsing Python code failed:\n{error}", @@ -174,6 +174,7 @@ class AstroidBuilder(raw_building.InspectBuilder): path=path, error=exc, ) from exc + if path is not None: node_file = os.path.abspath(path) else: @@ -186,7 +187,7 @@ class AstroidBuilder(raw_building.InspectBuilder): path is not None and os.path.splitext(os.path.basename(path))[0] == "__init__" ) - builder = rebuilder.TreeRebuilder(self._manager) + builder = rebuilder.TreeRebuilder(self._manager, parser_module) module = builder.visit_module(node, modname, node_file, package) module._import_from_nodes = builder._import_from_nodes module._delayed_assattr = builder._delayed_assattr @@ -438,17 +439,17 @@ def extract_node(code, module_name=""): return extracted -MISPLACED_TYPE_ANNOTATION_ERROR = "misplaced type annotation" - - def _parse_string(data, type_comments=True): + parser_module = get_parser_module(type_comments=type_comments) try: - node = _parse(data + "\n", type_comments=type_comments) + parsed = parser_module.parse(data + "\n", type_comments=type_comments) except SyntaxError as exc: # If the type annotations are misplaced for some reason, we do not want # to fail the entire parsing of the file, so we need to retry the parsing without # type comment support. if exc.args[0] != MISPLACED_TYPE_ANNOTATION_ERROR or not type_comments: raise - node = _parse(data + "\n", type_comments=False) - return node + + parser_module = get_parser_module(type_comments=False) + parsed = parser_module.parse(data + "\n", type_comments=False) + return parsed, parser_module diff --git a/astroid/context.py b/astroid/context.py index 113cac6b..82a80a06 100644 --- a/astroid/context.py +++ b/astroid/context.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> diff --git a/astroid/decorators.py b/astroid/decorators.py index 14487570..0f3632c4 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -3,6 +3,7 @@ # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz> # Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk> # Copyright (c) 2018 HoverHell <hoverhell@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> diff --git a/astroid/exceptions.py b/astroid/exceptions.py index 7e9d655e..08e72c13 100644 --- a/astroid/exceptions.py +++ b/astroid/exceptions.py @@ -28,7 +28,7 @@ class AstroidError(Exception): """ def __init__(self, message="", **kws): - super(AstroidError, self).__init__(message) + super().__init__(message) self.message = message for key, value in kws.items(): setattr(self, key, value) @@ -46,7 +46,7 @@ class AstroidBuildingError(AstroidError): """ def __init__(self, message="Failed to import module {modname}.", **kws): - super(AstroidBuildingError, self).__init__(message, **kws) + super().__init__(message, **kws) class AstroidImportError(AstroidBuildingError): @@ -69,7 +69,7 @@ class TooManyLevelsError(AstroidImportError): message="Relative import with too many levels " "({level}) for module {name!r}", **kws ): - super(TooManyLevelsError, self).__init__(message, **kws) + super().__init__(message, **kws) class AstroidSyntaxError(AstroidBuildingError): @@ -89,7 +89,7 @@ class NoDefault(AstroidError): name = None def __init__(self, message="{func!r} has no default for {name!r}.", **kws): - super(NoDefault, self).__init__(message, **kws) + super().__init__(message, **kws) class ResolveError(AstroidError): @@ -157,7 +157,7 @@ class InferenceError(ResolveError): context = None def __init__(self, message="Inference failed for {node!r}.", **kws): - super(InferenceError, self).__init__(message, **kws) + super().__init__(message, **kws) # Why does this inherit from InferenceError rather than ResolveError? @@ -175,7 +175,7 @@ class NameInferenceError(InferenceError): scope = None def __init__(self, message="{name!r} not found in {scope!r}.", **kws): - super(NameInferenceError, self).__init__(message, **kws) + super().__init__(message, **kws) class AttributeInferenceError(ResolveError): @@ -191,7 +191,7 @@ class AttributeInferenceError(ResolveError): attribute = None def __init__(self, message="{attribute!r} not found on {target!r}.", **kws): - super(AttributeInferenceError, self).__init__(message, **kws) + super().__init__(message, **kws) class UseInferenceDefault(Exception): diff --git a/astroid/helpers.py b/astroid/helpers.py index be133b38..8ab68799 100644 --- a/astroid/helpers.py +++ b/astroid/helpers.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> @@ -190,9 +190,9 @@ def _type_check(type1, type2): return False try: return type1 in type2.mro()[:-1] - except exceptions.MroError: + except exceptions.MroError as e: # The MRO is invalid. - raise exceptions._NonDeducibleTypeHierarchy + raise exceptions._NonDeducibleTypeHierarchy from e def is_subtype(type1, type2): @@ -254,13 +254,17 @@ def object_len(node, context=None): return len(inferred_node.elts) if isinstance(inferred_node, nodes.Dict): return len(inferred_node.items) + + node_type = object_type(inferred_node, context=context) + if not node_type: + raise exceptions.InferenceError(node=node) + try: - node_type = object_type(inferred_node, context=context) len_call = next(node_type.igetattr("__len__", context=context)) - except exceptions.AttributeInferenceError: + except exceptions.AttributeInferenceError as e: raise exceptions.AstroidTypeError( - "object of type '{}' has no len()".format(len_call.pytype()) - ) + "object of type '{}' has no len()".format(node_type.pytype()) + ) from e result_of_len = next(len_call.infer_call_result(node, context)) if ( @@ -268,6 +272,11 @@ def object_len(node, context=None): and result_of_len.pytype() == "builtins.int" ): return result_of_len.value + if isinstance(result_of_len, bases.Instance) and result_of_len.is_subtype_of( + "builtins.int" + ): + # Fake a result as we don't know the arguments of the instance call. + return 0 raise exceptions.AstroidTypeError( "'{}' object cannot be interpreted as an integer".format(result_of_len) ) diff --git a/astroid/inference.py b/astroid/inference.py index a563287d..bc3e1f97 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -2,7 +2,7 @@ # Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com> # Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru> @@ -10,10 +10,13 @@ # Copyright (c) 2017 Michał Masłowski <m.maslowski@clearcode.cc> # Copyright (c) 2017 Calen Pennington <cale@edx.org> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> +# Copyright (c) 2018-2019 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2018 Daniel Martin <daniel.martin@crowdstrike.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> -# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> # Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk> # Copyright (c) 2018 HoverHell <hoverhell@gmail.com> +# Copyright (c) 2020 Leandro T. C. Melo <ltcmelo@gmail.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/master/COPYING.LESSER @@ -25,6 +28,7 @@ import functools import itertools import operator +import wrapt from astroid import bases from astroid import context as contextmod from astroid import exceptions @@ -307,6 +311,8 @@ def infer_attribute(self, context=None): except exceptions._NonDeducibleTypeHierarchy: # Can't determine anything useful. pass + elif not context: + context = contextmod.InferenceContext() try: context.boundnode = owner @@ -348,7 +354,6 @@ nodes.Global._infer = infer_global _SUBSCRIPT_SENTINEL = object() -@decorators.raise_if_nothing_inferred def infer_subscript(self, context=None): """Inference for subscripts @@ -405,8 +410,10 @@ def infer_subscript(self, context=None): return None -nodes.Subscript._infer = decorators.path_wrapper(infer_subscript) -nodes.Subscript.infer_lhs = infer_subscript +nodes.Subscript._infer = decorators.raise_if_nothing_inferred( + decorators.path_wrapper(infer_subscript) +) +nodes.Subscript.infer_lhs = decorators.raise_if_nothing_inferred(infer_subscript) @decorators.raise_if_nothing_inferred @@ -837,9 +844,8 @@ def infer_assign(self, context=None): """infer a AssignName/AssignAttr: need to inspect the RHS part of the assign node """ - stmt = self.statement() - if isinstance(stmt, nodes.AugAssign): - return stmt.infer(context) + if isinstance(self.parent, nodes.AugAssign): + return self.parent.infer(context) stmts = list(self.assigned_stmts(context=context)) return bases._infer_stmts(stmts, context) @@ -947,10 +953,30 @@ def infer_ifexp(self, context=None): nodes.IfExp._infer = infer_ifexp +# pylint: disable=dangerous-default-value +@wrapt.decorator +def _cached_generator(func, instance, args, kwargs, _cache={}): + node = args[0] + try: + return iter(_cache[func, id(node)]) + except KeyError: + result = func(*args, **kwargs) + # Need to keep an iterator around + original, copy = itertools.tee(result) + _cache[func, id(node)] = list(copy) + return original + + +# When inferring a property, we instantiate a new `objects.Property` object, +# which in turn, because it inherits from `FunctionDef`, sets itself in the locals +# of the wrapping frame. This means that everytime we infer a property, the locals +# are mutated with a new instance of the property. This is why we cache the result +# of the function's inference. +@_cached_generator def infer_functiondef(self, context=None): if not self.decorators or not bases._is_property(self): yield self - return + return dict(node=self, context=context) prop_func = objects.Property( function=self, @@ -962,6 +988,7 @@ def infer_functiondef(self, context=None): ) prop_func.postinit(body=[], args=self.args) yield prop_func + return dict(node=self, context=context) nodes.FunctionDef._infer = infer_functiondef diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 84e093bb..3cf5fea5 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -5,6 +5,8 @@ # Copyright (c) 2017 ioanatia <ioanatia@users.noreply.github.com> # Copyright (c) 2017 Calen Pennington <cale@edx.org> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> import abc import collections @@ -182,7 +184,7 @@ class ZipFinder(Finder): """Finder that knows how to find a module inside zip files.""" def __init__(self, path): - super(ZipFinder, self).__init__(path) + super().__init__(path) self._zipimporters = _precache_zipimporters(path) def find_module(self, modname, module_parts, processed, submodule_path): diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py index 57d9da09..10c659f6 100644 --- a/astroid/interpreter/objectmodel.py +++ b/astroid/interpreter/objectmodel.py @@ -1,8 +1,10 @@ -# Copyright (c) 2016-2018 Claudiu Popa <pcmanticore@gmail.com> +# -*- coding: utf-8 -*- +# Copyright (c) 2016-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com> # Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2017 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2017 Calen Pennington <cale@edx.org> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.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/master/COPYING.LESSER @@ -657,9 +659,16 @@ class ImportErrorInstanceModel(ExceptionInstanceModel): return node_classes.Const("") +class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel): + @property + def attr_object(self): + return node_classes.Const("") + + BUILTIN_EXCEPTIONS = { "builtins.SyntaxError": SyntaxErrorInstanceModel, "builtins.ImportError": ImportErrorInstanceModel, + "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel, # These are all similar to OSError in terms of attributes "builtins.OSError": OSErrorInstanceModel, "builtins.BlockingIOError": OSErrorInstanceModel, @@ -741,6 +750,27 @@ class PropertyModel(ObjectModel): """Model for a builtin property""" # pylint: disable=import-outside-toplevel + def _init_function(self, name): + from astroid.node_classes import Arguments + from astroid.scoped_nodes import FunctionDef + + args = Arguments() + args.postinit( + args=[], + defaults=[], + kwonlyargs=[], + kw_defaults=[], + annotations=[], + posonlyargs=[], + posonlyargs_annotations=[], + kwonlyargs_annotations=[], + ) + + function = FunctionDef(name=name, parent=self._instance) + + function.postinit(args=args, body=[]) + return function + @property def attr_fget(self): from astroid.scoped_nodes import FunctionDef @@ -765,20 +795,14 @@ class PropertyModel(ObjectModel): @property def attr_setter(self): - from astroid.scoped_nodes import FunctionDef - - return FunctionDef(name="setter", parent=self._instance) + return self._init_function("setter") @property def attr_deleter(self): - from astroid.scoped_nodes import FunctionDef - - return FunctionDef(name="deleter", parent=self._instance) + return self._init_function("deleter") @property def attr_getter(self): - from astroid.scoped_nodes import FunctionDef - - return FunctionDef(name="getter", parent=self._instance) + return self._init_function("getter") # pylint: enable=import-outside-toplevel diff --git a/astroid/manager.py b/astroid/manager.py index 4dad678a..82208adf 100644 --- a/astroid/manager.py +++ b/astroid/manager.py @@ -1,5 +1,5 @@ # Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 BioGeek <jeroen.vangoey@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> @@ -8,6 +8,9 @@ # Copyright (c) 2017 Iva Miholic <ivamiho@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2019 Raphael Gaschignard <raphael@makeleaps.com> +# Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.com> +# Copyright (c) 2020 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -99,6 +102,13 @@ class AstroidManager: "Unable to build an AST for {path}.", path=filepath ) + def ast_from_string(self, data, modname="", filepath=None): + """ Given some source code as a string, return its corresponding astroid object""" + # pylint: disable=import-outside-toplevel; circular import + from astroid.builder import AstroidBuilder + + return AstroidBuilder(self).string_build(data, modname, filepath) + def _build_stub_module(self, modname): # pylint: disable=import-outside-toplevel; circular import from astroid.builder import AstroidBuilder @@ -168,6 +178,8 @@ class AstroidManager: return self._build_namespace_module( modname, found_spec.submodule_search_locations ) + elif found_spec.type == spec.ModuleType.PY_FROZEN: + return self._build_stub_module(modname) if found_spec.location is None: raise exceptions.AstroidImportError( diff --git a/astroid/modutils.py b/astroid/modutils.py index 0c009b13..4e6ed86b 100644 --- a/astroid/modutils.py +++ b/astroid/modutils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2018, 2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Denis Laxalde <denis.laxalde@logilab.fr> # Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> @@ -9,9 +9,13 @@ # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Mario Corchero <mcorcherojim@bloomberg.net> # Copyright (c) 2018 Mario Corchero <mariocj89@gmail.com> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com> +# Copyright (c) 2019 markmcclain <markmcclain@users.noreply.github.com> +# Copyright (c) 2019 BasPH <BasPH@users.noreply.github.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/master/COPYING.LESSER @@ -40,6 +44,7 @@ from distutils.errors import DistutilsPlatformError # distutils is replaced by virtualenv with a module that does # weird path manipulations in order to get to the # real distutils module. +from typing import Optional, List from .interpreter._import import spec from .interpreter._import import util @@ -198,16 +203,15 @@ def load_module_from_name(dotted_name, path=None, use_sys=True): return load_module_from_modpath(dotted_name.split("."), path, use_sys) -def load_module_from_modpath(parts, path=None, use_sys=1): +def load_module_from_modpath(parts, path: Optional[List[str]] = None, use_sys=1): """Load a python module from its split name. :type parts: list(str) or tuple(str) :param parts: python name of a module or package split on '.' - :type path: list or None :param path: - optional list of path where the module or package should be + Optional list of path where the module or package should be searched (use sys.path if nothing or None is given) :type use_sys: bool @@ -254,15 +258,16 @@ def load_module_from_modpath(parts, path=None, use_sys=1): return module -def load_module_from_file(filepath, path=None, use_sys=True, extrapath=None): +def load_module_from_file( + filepath: str, path: Optional[List[str]] = None, use_sys=True +): """Load a Python module from it's path. :type filepath: str :param filepath: path to the python module or package - :type path: list or None - :param path: - optional list of path where the module or package should be + :param Optional[List[str]] path: + Optional list of path where the module or package should be searched (use sys.path if nothing or None is given) :type use_sys: bool @@ -270,13 +275,12 @@ def load_module_from_file(filepath, path=None, use_sys=True, extrapath=None): boolean indicating whether the sys.modules dictionary should be used or not - :raise ImportError: if the module or package is not found :rtype: module :return: the loaded module """ - modpath = modpath_from_file(filepath, extrapath) + modpath = modpath_from_file(filepath) return load_module_from_modpath(modpath, path, use_sys) @@ -327,28 +331,18 @@ def _get_relative_base_path(filename, path_to_check): return None -def modpath_from_file_with_callback(filename, extrapath=None, is_package_cb=None): +def modpath_from_file_with_callback(filename, path=None, is_package_cb=None): filename = os.path.expanduser(_path_from_filename(filename)) - - if extrapath is not None: - for path_ in itertools.chain(map(_canonicalize_path, extrapath), extrapath): - path = os.path.abspath(path_) - if not path: - continue - submodpath = _get_relative_base_path(filename, path) - if not submodpath: - continue - if is_package_cb(path, submodpath[:-1]): - return extrapath[path_].split(".") + submodpath - - for path in itertools.chain(map(_canonicalize_path, sys.path), sys.path): - path = _cache_normalize_path(path) - if not path: + for pathname in itertools.chain( + path or [], map(_canonicalize_path, sys.path), sys.path + ): + pathname = _cache_normalize_path(pathname) + if not pathname: continue - modpath = _get_relative_base_path(filename, path) + modpath = _get_relative_base_path(filename, pathname) if not modpath: continue - if is_package_cb(path, modpath[:-1]): + if is_package_cb(pathname, modpath[:-1]): return modpath raise ImportError( @@ -356,19 +350,17 @@ def modpath_from_file_with_callback(filename, extrapath=None, is_package_cb=None ) -def modpath_from_file(filename, extrapath=None): - """given a file path return the corresponding split module's name - (i.e name of a module or package split on '.') +def modpath_from_file(filename, path=None): + """Get the corresponding split module's name from a filename + + This function will return the name of a module or package split on `.`. :type filename: str :param filename: file's path for which we want the module's name - :type extrapath: dict - :param extrapath: - optional extra search path, with path as key and package name for the path - as value. This is usually useful to handle package split in multiple - directories using __path__ trick. - + :type Optional[List[str]] path: + Optional list of path where the module or package should be + searched (use sys.path if nothing or None is given) :raise ImportError: if the corresponding module's name has not been found @@ -376,7 +368,7 @@ def modpath_from_file(filename, extrapath=None): :rtype: list(str) :return: the corresponding split module's name """ - return modpath_from_file_with_callback(filename, extrapath, check_modpath_has_init) + return modpath_from_file_with_callback(filename, path, check_modpath_has_init) def file_from_modpath(modpath, path=None, context_file=None): diff --git a/astroid/node_classes.py b/astroid/node_classes.py index b1344723..59b1e31c 100644 --- a/astroid/node_classes.py +++ b/astroid/node_classes.py @@ -3,7 +3,7 @@ # Copyright (c) 2010 Daniel Harding <dharding@gmail.com> # Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com> # Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> @@ -11,13 +11,17 @@ # Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2016 Dave Baum <dbaum@google.com> -# Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk> -# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> +# Copyright (c) 2017-2020 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017 rr- <rr-@sakuya.pl> +# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com> -# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> # Copyright (c) 2018 HoverHell <hoverhell@gmail.com> +# Copyright (c) 2019 kavins14 <kavin.singh@mail.utoronto.ca> +# Copyright (c) 2019 kavins14 <kavinsingh@hotmail.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/master/COPYING.LESSER @@ -897,7 +901,7 @@ class NodeNG: _repr_tree(self, result, set()) return "".join(result) - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. The boolean value of a node can have three @@ -986,7 +990,7 @@ class _BaseContainer( :type: list(NodeNG) """ - super(_BaseContainer, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, elts): """Do some setup after initialisation. @@ -1021,7 +1025,7 @@ class _BaseContainer( """ return self.elts - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -1145,10 +1149,9 @@ class LookupMixIn: _stmts = [] _stmt_parents = [] statements = self._get_filtered_node_statements(stmts) - for node, stmt in statements: # line filtering is on and we have reached our location, break - if stmt.fromlineno > mylineno > 0: + if stmt.fromlineno and stmt.fromlineno > mylineno > 0: break # Ignore decorators with the same name as the # decorated function @@ -1282,7 +1285,7 @@ class AssignName( :type: str or None """ - super(AssignName, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) class DelName( @@ -1322,7 +1325,7 @@ class DelName( :type: str or None """ - super(DelName, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) class Name(mixins.NoChildrenMixin, LookupMixIn, NodeNG): @@ -1363,7 +1366,7 @@ class Name(mixins.NoChildrenMixin, LookupMixIn, NodeNG): :type: str or None """ - super(Name, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def _get_name_nodes(self): yield self @@ -1395,18 +1398,21 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): # - we expose 'annotation', a list with annotations for # for each normal argument. If an argument doesn't have an # annotation, its value will be None. - + # pylint: disable=too-many-instance-attributes _astroid_fields = ( "args", "defaults", "kwonlyargs", "posonlyargs", + "posonlyargs_annotations", "kw_defaults", "annotations", "varargannotation", "kwargannotation", "kwonlyargs_annotations", "type_comment_args", + "type_comment_kwonlyargs", + "type_comment_posonlyargs", ) varargannotation = None """The type annotation for the variable length arguments. @@ -1432,7 +1438,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): :param parent: The parent node in the syntax tree. :type parent: NodeNG or None """ - super(Arguments, self).__init__(parent=parent) + super().__init__(parent=parent) self.vararg = vararg """The name of the variable length arguments. @@ -1502,6 +1508,24 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): :type: list(NodeNG or None) """ + self.type_comment_kwonlyargs = [] + """The type annotation, passed by a type comment, of each keyword only argument. + + If an argument does not have a type comment, + the value for that argument will be None. + + :type: list(NodeNG or None) + """ + + self.type_comment_posonlyargs = [] + """The type annotation, passed by a type comment, of each positional argument. + + If an argument does not have a type comment, + the value for that argument will be None. + + :type: list(NodeNG or None) + """ + # pylint: disable=too-many-arguments def postinit( self, @@ -1516,6 +1540,8 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): varargannotation=None, kwargannotation=None, type_comment_args=None, + type_comment_kwonlyargs=None, + type_comment_posonlyargs=None, ): """Do some setup after initialisation. @@ -1563,6 +1589,14 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): :param type_comment_args: The type annotation, passed by a type comment, of each argument. :type type_comment_args: list(NodeNG or None) + + :param type_comment_args: The type annotation, + passed by a type comment, of each keyword only argument. + :type type_comment_args: list(NodeNG or None) + + :param type_comment_args: The type annotation, + passed by a type comment, of each positional argument. + :type type_comment_args: list(NodeNG or None) """ self.args = args self.defaults = defaults @@ -1575,6 +1609,8 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): self.varargannotation = varargannotation self.kwargannotation = kwargannotation self.type_comment_args = type_comment_args + self.type_comment_kwonlyargs = type_comment_kwonlyargs + self.type_comment_posonlyargs = type_comment_posonlyargs # pylint: disable=too-many-arguments @@ -1589,7 +1625,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): :type: int or None """ - lineno = super(Arguments, self).fromlineno + lineno = super().fromlineno return max(lineno, self.parent.fromlineno or 0) @decorators.cachedproperty @@ -1612,7 +1648,13 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): positional_only_defaults = self.defaults[: len(self.defaults) - len(args)] if self.posonlyargs: - result.append(_format_args(self.posonlyargs, positional_only_defaults)) + result.append( + _format_args( + self.posonlyargs, + positional_only_defaults, + self.posonlyargs_annotations, + ) + ) result.append("/") if self.args: result.append( @@ -1695,6 +1737,11 @@ class Arguments(mixins.AssignTypeMixin, NodeNG): def get_children(self): yield from self.posonlyargs or () + + for elt in self.posonlyargs_annotations: + if elt is not None: + yield elt + yield from self.args or () yield from self.defaults @@ -1798,7 +1845,7 @@ class AssignAttr(mixins.ParentAssignTypeMixin, NodeNG): :type: str or None """ - super(AssignAttr, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, expr=None): """Do some setup after initialisation. @@ -2017,7 +2064,7 @@ class AugAssign(mixins.AssignTypeMixin, Statement): :type: str or None """ - super(AugAssign, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, target=None, value=None): """Do some setup after initialisation. @@ -2130,7 +2177,7 @@ class BinOp(NodeNG): :type: str or None """ - super(BinOp, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, left=None, right=None): """Do some setup after initialisation. @@ -2218,7 +2265,7 @@ class BoolOp(NodeNG): :type: str or None """ - super(BoolOp, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, values=None): """Do some setup after initialisation. @@ -2416,7 +2463,7 @@ class Comprehension(NodeNG): :param parent: The parent node in the syntax tree. :type parent: NodeNG or None """ - super(Comprehension, self).__init__() + super().__init__() self.parent = parent # pylint: disable=redefined-builtin; same name as builtin ast module. @@ -2513,7 +2560,7 @@ class Const(mixins.NoChildrenMixin, NodeNG, bases.Instance): :type: object """ - super(Const, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def __getattr__(self, name): # This is needed because of Proxy's __getattr__ method. @@ -2592,7 +2639,7 @@ class Const(mixins.NoChildrenMixin, NodeNG, bases.Instance): """ return self._proxied.qname() - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -2694,7 +2741,7 @@ class DelAttr(mixins.ParentAssignTypeMixin, NodeNG): :type: str or None """ - super(DelAttr, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, expr=None): """Do some setup after initialisation. @@ -2767,7 +2814,7 @@ class Dict(NodeNG, bases.Instance): :type: list(tuple(NodeNG, NodeNG)) """ - super(Dict, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, items): """Do some setup after initialisation. @@ -2865,7 +2912,7 @@ class Dict(NodeNG, bases.Instance): raise exceptions.AstroidIndexError(index) - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -2920,7 +2967,7 @@ class Ellipsis(mixins.NoChildrenMixin, NodeNG): # pylint: disable=redefined-bui <Ellipsis l.1 at 0x7f23b2e35160> """ - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -3299,7 +3346,7 @@ class ImportFrom(mixins.NoChildrenMixin, mixins.ImportFromMixin, Statement): :type: int """ - super(ImportFrom, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) class Attribute(NodeNG): @@ -3334,7 +3381,7 @@ class Attribute(NodeNG): :type: str or None """ - super(Attribute, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, expr=None): """Do some setup after initialisation. @@ -3379,7 +3426,7 @@ class Global(mixins.NoChildrenMixin, Statement): :type: list(str) """ - super(Global, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def _infer_name(self, frame, name): return name @@ -3460,6 +3507,11 @@ class If(mixins.MultiLineBlockMixin, mixins.BlockRangeMixIn, Statement): def has_elif_block(self): return len(self.orelse) == 1 and isinstance(self.orelse[0], If) + def _get_yield_nodes_skip_lambdas(self): + """An If node can contain a Yield node in the test""" + yield from self.test._get_yield_nodes_skip_lambdas() + yield from super()._get_yield_nodes_skip_lambdas() + class IfExp(NodeNG): """Class representing an :class:`ast.IfExp` node. @@ -3547,7 +3599,7 @@ class Import(mixins.NoChildrenMixin, mixins.ImportFromMixin, Statement): :type: list(tuple(str, str or None)) or None """ - super(Import, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) class Index(NodeNG): @@ -3620,7 +3672,7 @@ class Keyword(NodeNG): :type: Name or None """ - super(Keyword, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, value=None): """Do some setup after initialisation. @@ -3665,7 +3717,7 @@ class List(_BaseContainer): :type: Context or None """ - super(List, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def pytype(self): """Get the name of the type that this node represents. @@ -3720,7 +3772,7 @@ class Nonlocal(mixins.NoChildrenMixin, Statement): :type: list(str) """ - super(Nonlocal, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def _infer_name(self, frame, name): return name @@ -3776,7 +3828,7 @@ class Print(Statement): :type: bool or None """ - super(Print, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, dest=None, values=None): """Do some setup after initialisation. @@ -4029,9 +4081,7 @@ class Starred(mixins.ParentAssignTypeMixin, NodeNG): :type: Context or None """ - super(Starred, self).__init__( - lineno=lineno, col_offset=col_offset, parent=parent - ) + super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) def postinit(self, value=None): """Do some setup after initialisation. @@ -4087,9 +4137,7 @@ class Subscript(NodeNG): :type: Context or None """ - super(Subscript, self).__init__( - lineno=lineno, col_offset=col_offset, parent=parent - ) + super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) # pylint: disable=redefined-builtin; had to use the same name as builtin ast module. def postinit(self, value=None, slice=None): @@ -4282,7 +4330,7 @@ class Tuple(_BaseContainer): :type: Context or None """ - super(Tuple, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def pytype(self): """Get the name of the type that this node represents. @@ -4338,7 +4386,7 @@ class UnaryOp(NodeNG): :type: str or None """ - super(UnaryOp, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, operand=None): """Do some setup after initialisation. @@ -4452,6 +4500,11 @@ class While(mixins.MultiLineBlockMixin, mixins.BlockRangeMixIn, Statement): yield from self.body yield from self.orelse + def _get_yield_nodes_skip_lambdas(self): + """A While node can contain a Yield node in the test""" + yield from self.test._get_yield_nodes_skip_lambdas() + yield from super()._get_yield_nodes_skip_lambdas() + class With( mixins.MultiLineBlockMixin, @@ -4694,6 +4747,42 @@ class Unknown(mixins.AssignTypeMixin, NodeNG): yield util.Uninferable +class EvaluatedObject(NodeNG): + """Contains an object that has already been inferred + + This class is useful to pre-evaluate a particular node, + with the resulting class acting as the non-evaluated node. + """ + + name = "EvaluatedObject" + _astroid_fields = ("original",) + _other_fields = ("value",) + + original = None + """The original node that has already been evaluated + + :type: NodeNG + """ + + value = None + """The inferred value + + :type: Union[Uninferable, NodeNG] + """ + + def __init__(self, original, value): + self.original = original + self.value = value + super().__init__( + lineno=self.original.lineno, + col_offset=self.original.col_offset, + parent=self.original.parent, + ) + + def infer(self, context=None, **kwargs): + yield self.value + + # constants ############################################################## CONST_CLS = { diff --git a/astroid/nodes.py b/astroid/nodes.py index bf6911ac..4ce4ebe2 100644 --- a/astroid/nodes.py +++ b/astroid/nodes.py @@ -1,6 +1,6 @@ # Copyright (c) 2006-2011, 2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2010 Daniel Harding <dharding@gmail.com> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com> @@ -86,6 +86,7 @@ from astroid.node_classes import ( # Node not present in the builtin ast module. DictUnpack, Unknown, + EvaluatedObject, ) from astroid.scoped_nodes import ( Module, diff --git a/astroid/objects.py b/astroid/objects.py index 93c5f406..fb782e6d 100644 --- a/astroid/objects.py +++ b/astroid/objects.py @@ -1,7 +1,8 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com> +# Copyright (c) 2018 hippo91 <guillaume.peillex@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html @@ -186,7 +187,12 @@ class Super(node_classes.NodeNG): yield inferred elif isinstance(inferred, Property): function = inferred.function - yield from function.infer_call_result(caller=self, context=context) + try: + yield from function.infer_call_result( + caller=self, context=context + ) + except exceptions.InferenceError: + yield util.Uninferable elif bases._is_property(inferred): # TODO: support other descriptors as well. try: @@ -291,8 +297,8 @@ class Property(scoped_nodes.FunctionDef): def __init__( self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None ): - super().__init__(name, doc, lineno, col_offset, parent) self.function = function + super().__init__(name, doc, lineno, col_offset, parent) # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor(lambda: objectmodel.PropertyModel()) diff --git a/astroid/protocols.py b/astroid/protocols.py index 33d90ea3..2cdf5548 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> @@ -9,9 +9,11 @@ # Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017 rr- <rr-@sakuya.pl> -# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> +# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 HoverHell <hoverhell@gmail.com> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER @@ -317,9 +319,14 @@ def _arguments_infer_argname(self, name, context): if not (self.arguments or self.vararg or self.kwarg): yield util.Uninferable return + + functype = self.parent.type # first argument of instance/class method - if self.arguments and getattr(self.arguments[0], "name", None) == name: - functype = self.parent.type + if ( + self.arguments + and getattr(self.arguments[0], "name", None) == name + and functype != "staticmethod" + ): cls = self.parent.parent.scope() is_metaclass = isinstance(cls, nodes.ClassDef) and cls.type == "metaclass" # If this is a metaclass, then the first argument will always @@ -367,7 +374,7 @@ def arguments_assigned_stmts(self, node=None, context=None, assign_path=None): callcontext = context.callcontext context = contextmod.copy_context(context) context.callcontext = None - args = arguments.CallSite(callcontext) + args = arguments.CallSite(callcontext, context=context) return args.infer_argument(self.parent, node.name, context) return _arguments_infer_argname(self, node.name, context) diff --git a/astroid/raw_building.py b/astroid/raw_building.py index d94f9240..b2612778 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -1,14 +1,17 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> # Copyright (c) 2015 Ovidiu Sabou <ovidiu@sabou.org> # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2020 Robin Jarry <robin.jarry@6wind.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/master/COPYING.LESSER @@ -273,6 +276,13 @@ def _build_from_function(node, name, member, module): object_build_function(node, member, name) +def _safe_has_attribute(obj, member): + try: + return hasattr(obj, member) + except Exception: # pylint: disable=broad-except + return False + + class InspectBuilder: """class for building nodes from living object @@ -354,6 +364,11 @@ class InspectBuilder: # This should be called for Jython, where some builtin # methods aren't caught by isbuiltin branch. _build_from_function(node, name, member, self._module) + elif _safe_has_attribute(member, "__all__"): + module = build_module(name) + _attach_local_node(node, module, name) + # recursion + self.object_build(module, member) else: # create an empty node so that the name is actually defined attach_dummy_node(node, name, member) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 10fd5900..3fc1a83f 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> -# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2013-2014 Google, Inc. # Copyright (c) 2014 Alexander Presnyakov <flagist0@gmail.com> # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> @@ -10,8 +10,14 @@ # Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017 rr- <rr-@sakuya.pl> +# Copyright (c) 2018-2019 Ville Skyttä <ville.skytta@iki.fi> +# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz> +# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019-2020 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com> +# Copyright (c) 2019 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -21,9 +27,10 @@ order to get a single Astroid representation """ import sys +from typing import Optional import astroid -from astroid._ast import _parse, _get_parser_module, parse_function_type_comment +from astroid._ast import parse_function_type_comment, get_parser_module, ParserModule from astroid import nodes @@ -41,57 +48,6 @@ PY37 = sys.version_info >= (3, 7) PY38 = sys.version_info >= (3, 8) -def _binary_operators_from_module(module): - binary_operators = { - module.Add: "+", - module.BitAnd: "&", - module.BitOr: "|", - module.BitXor: "^", - module.Div: "/", - module.FloorDiv: "//", - module.MatMult: "@", - module.Mod: "%", - module.Mult: "*", - module.Pow: "**", - module.Sub: "-", - module.LShift: "<<", - module.RShift: ">>", - } - return binary_operators - - -def _bool_operators_from_module(module): - return {module.And: "and", module.Or: "or"} - - -def _unary_operators_from_module(module): - return {module.UAdd: "+", module.USub: "-", module.Not: "not", module.Invert: "~"} - - -def _compare_operators_from_module(module): - return { - module.Eq: "==", - module.Gt: ">", - module.GtE: ">=", - module.In: "in", - module.Is: "is", - module.IsNot: "is not", - module.Lt: "<", - module.LtE: "<=", - module.NotEq: "!=", - module.NotIn: "not in", - } - - -def _contexts_from_module(module): - return { - module.Load: astroid.Load, - module.Store: astroid.Store, - module.Del: astroid.Del, - module.Param: astroid.Store, - } - - def _visit_or_none(node, attr, visitor, parent, visit="visit", **kws): """If the given node has an attribute, visits the attribute, and otherwise returns None. @@ -107,32 +63,30 @@ def _visit_or_none(node, attr, visitor, parent, visit="visit", **kws): class TreeRebuilder: """Rebuilds the _ast tree to become an Astroid tree""" - def __init__(self, manager, parse_python_two: bool = False): + def __init__(self, manager, parser_module: Optional[ParserModule] = None): self._manager = manager self._global_names = [] self._import_from_nodes = [] self._delayed_assattr = [] self._visit_meths = {} - # Configure the right classes for the right module - self._parser_module = _get_parser_module(parse_python_two=parse_python_two) - self._unary_op_classes = _unary_operators_from_module(self._parser_module) - self._cmp_op_classes = _compare_operators_from_module(self._parser_module) - self._bool_op_classes = _bool_operators_from_module(self._parser_module) - self._bin_op_classes = _binary_operators_from_module(self._parser_module) - self._context_classes = _contexts_from_module(self._parser_module) + if parser_module is None: + self._parser_module = get_parser_module() + else: + self._parser_module = parser_module + self._module = self._parser_module.module def _get_doc(self, node): try: if PY37 and hasattr(node, "docstring"): doc = node.docstring return node, doc - if node.body and isinstance(node.body[0], self._parser_module.Expr): + if node.body and isinstance(node.body[0], self._module.Expr): first_value = node.body[0].value - if isinstance(first_value, self._parser_module.Str) or ( + if isinstance(first_value, self._module.Str) or ( PY38 - and isinstance(first_value, self._parser_module.Constant) + and isinstance(first_value, self._module.Constant) and isinstance(first_value.value, str) ): doc = first_value.value if PY38 else first_value.s @@ -143,7 +97,7 @@ class TreeRebuilder: return node, None def _get_context(self, node): - return self._context_classes.get(type(node.ctx), astroid.Load) + return self._parser_module.context_classes.get(type(node.ctx), astroid.Load) def visit_module(self, node, modname, modpath, package): """visit a Module node by returning a fresh instance of it""" @@ -177,6 +131,10 @@ class TreeRebuilder: else: node.parent.set_local(node.name, node) + def visit_arg(self, node, parent): + """visit an arg node by returning a fresh AssName instance""" + return self.visit_assignname(node, parent, node.arg) + def visit_arguments(self, node, parent): """visit an Arguments node by returning a fresh instance of it""" vararg, kwarg = node.vararg, node.kwarg @@ -221,6 +179,15 @@ class TreeRebuilder: type_comment_args = [ self.check_type_comment(child, parent=newnode) for child in node.args ] + type_comment_kwonlyargs = [ + self.check_type_comment(child, parent=newnode) for child in node.kwonlyargs + ] + type_comment_posonlyargs = [] + if PY38: + type_comment_posonlyargs = [ + self.check_type_comment(child, parent=newnode) + for child in node.posonlyargs + ] newnode.postinit( args=args, @@ -234,6 +201,8 @@ class TreeRebuilder: varargannotation=varargannotation, kwargannotation=kwargannotation, type_comment_args=type_comment_args, + type_comment_kwonlyargs=type_comment_kwonlyargs, + type_comment_posonlyargs=type_comment_posonlyargs, ) # save argument names in locals: if vararg: @@ -258,7 +227,7 @@ class TreeRebuilder: return None try: - type_comment_ast = _parse(type_comment) + type_comment_ast = self._parser_module.parse(type_comment) except SyntaxError: # Invalid type comment, just skip it. return None @@ -289,6 +258,21 @@ class TreeRebuilder: return returns, argtypes + # Async structs added in Python 3.5 + def visit_asyncfunctiondef(self, node, parent): + return self._visit_functiondef(nodes.AsyncFunctionDef, node, parent) + + def visit_asyncfor(self, node, parent): + return self._visit_for(nodes.AsyncFor, node, parent) + + def visit_await(self, node, parent): + newnode = nodes.Await(node.lineno, node.col_offset, parent) + newnode.postinit(value=self.visit(node.value, newnode)) + return newnode + + def visit_asyncwith(self, node, parent): + return self._visit_with(nodes.AsyncWith, node, parent) + def visit_assign(self, node, parent): """visit a Assign node by returning a fresh instance of it""" newnode = nodes.Assign(node.lineno, node.col_offset, parent) @@ -300,6 +284,18 @@ class TreeRebuilder: ) return newnode + def visit_annassign(self, node, parent): + """visit an AnnAssign node by returning a fresh instance of it""" + newnode = nodes.AnnAssign(node.lineno, node.col_offset, parent) + annotation = _visit_or_none(node, "annotation", self, newnode) + newnode.postinit( + target=self.visit(node.target, newnode), + annotation=annotation, + simple=node.simple, + value=_visit_or_none(node, "value", self, newnode), + ) + return newnode + def visit_assignname(self, node, parent, node_name=None): """visit a node and return a AssignName node""" newnode = nodes.AssignName( @@ -314,7 +310,7 @@ class TreeRebuilder: def visit_augassign(self, node, parent): """visit a AugAssign node by returning a fresh instance of it""" newnode = nodes.AugAssign( - self._bin_op_classes[type(node.op)] + "=", + self._parser_module.bin_op_classes[type(node.op)] + "=", node.lineno, node.col_offset, parent, @@ -333,7 +329,10 @@ class TreeRebuilder: def visit_binop(self, node, parent): """visit a BinOp node by returning a fresh instance of it""" newnode = nodes.BinOp( - self._bin_op_classes[type(node.op)], node.lineno, node.col_offset, parent + self._parser_module.bin_op_classes[type(node.op)], + node.lineno, + node.col_offset, + parent, ) newnode.postinit( self.visit(node.left, newnode), self.visit(node.right, newnode) @@ -343,7 +342,10 @@ class TreeRebuilder: def visit_boolop(self, node, parent): """visit a BoolOp node by returning a fresh instance of it""" newnode = nodes.BoolOp( - self._bool_op_classes[type(node.op)], node.lineno, node.col_offset, parent + self._parser_module.bool_op_classes[type(node.op)], + node.lineno, + node.col_offset, + parent, ) newnode.postinit([self.visit(child, newnode) for child in node.values]) return newnode @@ -389,7 +391,7 @@ class TreeRebuilder: newnode.postinit(self.visit(node.func, newnode), args, keywords) return newnode - def visit_classdef(self, node, parent, newstyle=None): + def visit_classdef(self, node, parent, newstyle=True): """visit a ClassDef node to become astroid""" node, doc = self._get_doc(node) newnode = nodes.ClassDef(node.name, doc, node.lineno, node.col_offset, parent) @@ -437,7 +439,10 @@ class TreeRebuilder: newnode.postinit( self.visit(node.left, newnode), [ - (self._cmp_op_classes[op.__class__], self.visit(expr, newnode)) + ( + self._parser_module.cmp_op_classes[op.__class__], + self.visit(expr, newnode), + ) for (op, expr) in zip(node.ops, node.comparators) ], ) @@ -524,10 +529,13 @@ class TreeRebuilder: def visit_excepthandler(self, node, parent): """visit an ExceptHandler node by returning a fresh instance of it""" newnode = nodes.ExceptHandler(node.lineno, node.col_offset, parent) - # /!\ node.name can be a tuple + if node.name: + name = self.visit_assignname(node, newnode, node.name) + else: + name = None newnode.postinit( _visit_or_none(node, "type", self, newnode), - _visit_or_none(node, "name", self, newnode), + name, [self.visit(child, newnode) for child in node.body], ) return newnode @@ -698,6 +706,27 @@ class TreeRebuilder: parent.set_local(name.split(".")[0], newnode) return newnode + def visit_joinedstr(self, node, parent): + newnode = nodes.JoinedStr(node.lineno, node.col_offset, parent) + newnode.postinit([self.visit(child, newnode) for child in node.values]) + return newnode + + def visit_formattedvalue(self, node, parent): + newnode = nodes.FormattedValue(node.lineno, node.col_offset, parent) + newnode.postinit( + self.visit(node.value, newnode), + node.conversion, + _visit_or_none(node, "format_spec", self, newnode), + ) + return newnode + + def visit_namedexpr(self, node, parent): + newnode = nodes.NamedExpr(node.lineno, node.col_offset, parent) + newnode.postinit( + self.visit(node.target, newnode), self.visit(node.value, newnode) + ) + return newnode + # Not used in Python 3.8+. def visit_index(self, node, parent): """visit a Index node by returning a fresh instance of it""" @@ -759,6 +788,25 @@ class TreeRebuilder: self._save_assignment(newnode) return newnode + # Not used in Python 3.8+. + def visit_nameconstant(self, node, parent): + # in Python 3.4 we have NameConstant for True / False / None + return nodes.Const( + node.value, + getattr(node, "lineno", None), + getattr(node, "col_offset", None), + parent, + ) + + def visit_nonlocal(self, node, parent): + """visit a Nonlocal node and return a new instance of it""" + return nodes.Nonlocal( + node.names, + getattr(node, "lineno", None), + getattr(node, "col_offset", None), + parent, + ) + def visit_constant(self, node, parent): """visit a Constant node by returning a fresh instance of Const""" return nodes.Const( @@ -806,11 +854,10 @@ class TreeRebuilder: def visit_raise(self, node, parent): """visit a Raise node by returning a fresh instance of it""" newnode = nodes.Raise(node.lineno, node.col_offset, parent) - # pylint: disable=too-many-function-args + # no traceback; anyway it is not used in Pylint newnode.postinit( - _visit_or_none(node, "type", self, newnode), - _visit_or_none(node, "inst", self, newnode), - _visit_or_none(node, "tback", self, newnode), + _visit_or_none(node, "exc", self, newnode), + _visit_or_none(node, "cause", self, newnode), ) return newnode @@ -857,6 +904,15 @@ class TreeRebuilder: ) return newnode + def visit_starred(self, node, parent): + """visit a Starred node and return a new instance of it""" + context = self._get_context(node) + newnode = nodes.Starred( + ctx=context, lineno=node.lineno, col_offset=node.col_offset, parent=parent + ) + newnode.postinit(self.visit(node.value, newnode)) + return newnode + def visit_tryexcept(self, node, parent): """visit a TryExcept node by returning a fresh instance of it""" newnode = nodes.TryExcept(node.lineno, node.col_offset, parent) @@ -867,6 +923,21 @@ class TreeRebuilder: ) return newnode + def visit_try(self, node, parent): + # python 3.3 introduce a new Try node replacing + # TryFinally/TryExcept nodes + if node.finalbody: + newnode = nodes.TryFinally(node.lineno, node.col_offset, parent) + if node.handlers: + body = [self.visit_tryexcept(node, newnode)] + else: + body = [self.visit(child, newnode) for child in node.body] + newnode.postinit(body, [self.visit(n, newnode) for n in node.finalbody]) + return newnode + if node.handlers: + return self.visit_tryexcept(node, parent) + return None + def visit_tryfinally(self, node, parent): """visit a TryFinally node by returning a fresh instance of it""" newnode = nodes.TryFinally(node.lineno, node.col_offset, parent) @@ -888,7 +959,7 @@ class TreeRebuilder: def visit_unaryop(self, node, parent): """visit a UnaryOp node by returning a fresh instance of it""" newnode = nodes.UnaryOp( - self._unary_op_classes[node.op.__class__], + self._parser_module.unary_op_classes[node.op.__class__], node.lineno, node.col_offset, parent, @@ -906,121 +977,7 @@ class TreeRebuilder: ) return newnode - def visit_with(self, node, parent): - newnode = nodes.With(node.lineno, node.col_offset, parent) - expr = self.visit(node.context_expr, newnode) - if node.optional_vars is not None: - optional_vars = self.visit(node.optional_vars, newnode) - else: - optional_vars = None - - type_annotation = self.check_type_comment(node, parent=newnode) - newnode.postinit( - items=[(expr, optional_vars)], - body=[self.visit(child, newnode) for child in node.body], - type_annotation=type_annotation, - ) - return newnode - - def visit_yield(self, node, parent): - """visit a Yield node by returning a fresh instance of it""" - newnode = nodes.Yield(node.lineno, node.col_offset, parent) - if node.value is not None: - newnode.postinit(self.visit(node.value, newnode)) - return newnode - - -class TreeRebuilder3(TreeRebuilder): - """extend and overwrite TreeRebuilder for python3k""" - - def visit_arg(self, node, parent): - """visit an arg node by returning a fresh AssName instance""" - return self.visit_assignname(node, parent, node.arg) - - # Not used in Python 3.8+. - def visit_nameconstant(self, node, parent): - # in Python 3.4 we have NameConstant for True / False / None - return nodes.Const( - node.value, - getattr(node, "lineno", None), - getattr(node, "col_offset", None), - parent, - ) - - def visit_excepthandler(self, node, parent): - """visit an ExceptHandler node by returning a fresh instance of it""" - newnode = nodes.ExceptHandler(node.lineno, node.col_offset, parent) - if node.name: - name = self.visit_assignname(node, newnode, node.name) - else: - name = None - newnode.postinit( - _visit_or_none(node, "type", self, newnode), - name, - [self.visit(child, newnode) for child in node.body], - ) - return newnode - - def visit_nonlocal(self, node, parent): - """visit a Nonlocal node and return a new instance of it""" - return nodes.Nonlocal( - node.names, - getattr(node, "lineno", None), - getattr(node, "col_offset", None), - parent, - ) - - def visit_raise(self, node, parent): - """visit a Raise node by returning a fresh instance of it""" - newnode = nodes.Raise(node.lineno, node.col_offset, parent) - # no traceback; anyway it is not used in Pylint - newnode.postinit( - _visit_or_none(node, "exc", self, newnode), - _visit_or_none(node, "cause", self, newnode), - ) - return newnode - - def visit_starred(self, node, parent): - """visit a Starred node and return a new instance of it""" - context = self._get_context(node) - newnode = nodes.Starred( - ctx=context, lineno=node.lineno, col_offset=node.col_offset, parent=parent - ) - newnode.postinit(self.visit(node.value, newnode)) - return newnode - - def visit_try(self, node, parent): - # python 3.3 introduce a new Try node replacing - # TryFinally/TryExcept nodes - if node.finalbody: - newnode = nodes.TryFinally(node.lineno, node.col_offset, parent) - if node.handlers: - body = [self.visit_tryexcept(node, newnode)] - else: - body = [self.visit(child, newnode) for child in node.body] - newnode.postinit(body, [self.visit(n, newnode) for n in node.finalbody]) - return newnode - if node.handlers: - return self.visit_tryexcept(node, parent) - return None - - def visit_annassign(self, node, parent): - """visit an AnnAssign node by returning a fresh instance of it""" - newnode = nodes.AnnAssign(node.lineno, node.col_offset, parent) - annotation = _visit_or_none(node, "annotation", self, newnode) - newnode.postinit( - target=self.visit(node.target, newnode), - annotation=annotation, - simple=node.simple, - value=_visit_or_none(node, "value", self, newnode), - ) - return newnode - def _visit_with(self, cls, node, parent): - if "items" not in node._fields: - # python < 3.3 - return super(TreeRebuilder3, self).visit_with(node, parent) - newnode = cls(node.lineno, node.col_offset, parent) def visit_child(child): @@ -1039,52 +996,15 @@ class TreeRebuilder3(TreeRebuilder): def visit_with(self, node, parent): return self._visit_with(nodes.With, node, parent) - def visit_yieldfrom(self, node, parent): - newnode = nodes.YieldFrom(node.lineno, node.col_offset, parent) + def visit_yield(self, node, parent): + """visit a Yield node by returning a fresh instance of it""" + newnode = nodes.Yield(node.lineno, node.col_offset, parent) if node.value is not None: newnode.postinit(self.visit(node.value, newnode)) return newnode - def visit_classdef(self, node, parent, newstyle=True): - return super(TreeRebuilder3, self).visit_classdef( - node, parent, newstyle=newstyle - ) - - # Async structs added in Python 3.5 - def visit_asyncfunctiondef(self, node, parent): - return self._visit_functiondef(nodes.AsyncFunctionDef, node, parent) - - def visit_asyncfor(self, node, parent): - return self._visit_for(nodes.AsyncFor, node, parent) - - def visit_await(self, node, parent): - newnode = nodes.Await(node.lineno, node.col_offset, parent) - newnode.postinit(value=self.visit(node.value, newnode)) - return newnode - - def visit_asyncwith(self, node, parent): - return self._visit_with(nodes.AsyncWith, node, parent) - - def visit_joinedstr(self, node, parent): - newnode = nodes.JoinedStr(node.lineno, node.col_offset, parent) - newnode.postinit([self.visit(child, newnode) for child in node.values]) - return newnode - - def visit_formattedvalue(self, node, parent): - newnode = nodes.FormattedValue(node.lineno, node.col_offset, parent) - newnode.postinit( - self.visit(node.value, newnode), - node.conversion, - _visit_or_none(node, "format_spec", self, newnode), - ) - return newnode - - def visit_namedexpr(self, node, parent): - newnode = nodes.NamedExpr(node.lineno, node.col_offset, parent) - newnode.postinit( - self.visit(node.target, newnode), self.visit(node.value, newnode) - ) + def visit_yieldfrom(self, node, parent): + newnode = nodes.YieldFrom(node.lineno, node.col_offset, parent) + if node.value is not None: + newnode.postinit(self.visit(node.value, newnode)) return newnode - - -TreeRebuilder = TreeRebuilder3 diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index ad727b13..8561e745 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -2,7 +2,7 @@ # Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2010 Daniel Harding <dharding@gmail.com> # Copyright (c) 2011, 2013-2015 Google, Inc. -# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2013 Phil Schaf <flying-sheep@web.de> # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Florian Bruhin <me@the-compiler.org> @@ -14,9 +14,12 @@ # Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017 David Euresti <david@dropbox.com> -# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2018-2019 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> # Copyright (c) 2018 HoverHell <hoverhell@gmail.com> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com> +# Copyright (c) 2019 Peter de Blanc <peter@standard.ai> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -50,6 +53,9 @@ BUILTINS = builtins.__name__ ITER_METHODS = ("__iter__", "__getitem__") EXCEPTION_BASE_CLASSES = frozenset({"Exception", "BaseException"}) objects = util.lazy_import("objects") +BUILTIN_DESCRIPTORS = frozenset( + {"classmethod", "staticmethod", "builtins.classmethod", "builtins.staticmethod"} +) def _c3_merge(sequences, cls, context): @@ -524,6 +530,11 @@ class Module(LocalsDictNodeNG): return "Module" def getattr(self, name, context=None, ignore_locals=False): + if not name: + raise exceptions.AttributeInferenceError( + target=self, attribute=name, context=context + ) + result = [] name_in_locals = name in self.locals @@ -734,7 +745,7 @@ class Module(LocalsDictNodeNG): """ return [name for name in self.keys() if not name.startswith("_")] - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -803,7 +814,7 @@ class GeneratorExp(ComprehensionScope): :type: dict(str, NodeNG) """ - super(GeneratorExp, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, elt=None, generators=None): """Do some setup after initialisation. @@ -820,7 +831,7 @@ class GeneratorExp(ComprehensionScope): else: self.generators = generators - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -879,7 +890,7 @@ class DictComp(ComprehensionScope): :type: dict(str, NodeNG) """ - super(DictComp, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, key=None, value=None, generators=None): """Do some setup after initialisation. @@ -900,7 +911,7 @@ class DictComp(ComprehensionScope): else: self.generators = generators - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -955,7 +966,7 @@ class SetComp(ComprehensionScope): :type: dict(str, NodeNG) """ - super(SetComp, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, elt=None, generators=None): """Do some setup after initialisation. @@ -972,7 +983,7 @@ class SetComp(ComprehensionScope): else: self.generators = generators - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -1019,7 +1030,7 @@ class _ListComp(node_classes.NodeNG): self.elt = elt self.generators = generators - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -1051,7 +1062,7 @@ class ListComp(_ListComp, ComprehensionScope): :type: dict(str, NodeNG) """ - super(ListComp, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def _infer_decorator_callchain(node): @@ -1073,6 +1084,21 @@ def _infer_decorator_callchain(node): return "classmethod" if result.is_subtype_of("%s.staticmethod" % BUILTINS): return "staticmethod" + if isinstance(result, FunctionDef): + if not result.decorators: + return None + # Determine if this function is decorated with one of the builtin descriptors we want. + for decorator in result.decorators.nodes: + if isinstance(decorator, node_classes.Name): + if decorator.name in BUILTIN_DESCRIPTORS: + return decorator.name + if ( + isinstance(decorator, node_classes.Attribute) + and isinstance(decorator.expr, node_classes.Name) + and decorator.expr.name == BUILTINS + and decorator.attrname in BUILTIN_DESCRIPTORS + ): + return decorator.attrname return None @@ -1136,7 +1162,7 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG): :type: list(NodeNG) """ - super(Lambda, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) def postinit(self, args, body): """Do some setup after initialisation. @@ -1243,7 +1269,7 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG): frame = self return frame._scope_lookup(node, name, offset) - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -1341,7 +1367,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): """ self.instance_attrs = {} - super(FunctionDef, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) if parent: frame = parent.frame() frame.set_local(name, self) @@ -1423,17 +1449,17 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): return decorators @decorators_mod.cachedproperty - def type(self): # pylint: disable=invalid-overridden-method + def type( + self + ): # pylint: disable=invalid-overridden-method,too-many-return-statements """The function type for this node. Possible values are: method, function, staticmethod, classmethod. :type: str """ - builtin_descriptors = {"classmethod", "staticmethod"} - for decorator in self.extra_decorators: - if decorator.func.name in builtin_descriptors: + if decorator.func.name in BUILTIN_DESCRIPTORS: return decorator.func.name frame = self.parent.frame() @@ -1451,8 +1477,15 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): for node in self.decorators.nodes: if isinstance(node, node_classes.Name): - if node.name in builtin_descriptors: + if node.name in BUILTIN_DESCRIPTORS: return node.name + if ( + isinstance(node, node_classes.Attribute) + and isinstance(node.expr, node_classes.Name) + and node.expr.name == BUILTINS + and node.attrname in BUILTIN_DESCRIPTORS + ): + return node.attrname if isinstance(node, node_classes.Call): # Handle the following case: @@ -1526,10 +1559,18 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): """this method doesn't look in the instance_attrs dictionary since it's done by an Instance proxy at inference time. """ + if not name: + raise exceptions.AttributeInferenceError( + target=self, attribute=name, context=context + ) + + found_attrs = [] if name in self.instance_attrs: - return self.instance_attrs[name] + found_attrs = self.instance_attrs[name] if name in self.special_attributes: - return [self.special_attributes.lookup(name)] + found_attrs.append(self.special_attributes.lookup(name)) + if found_attrs: + return found_attrs raise exceptions.AttributeInferenceError(target=self, attribute=name) def igetattr(self, name, context=None): @@ -1620,7 +1661,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): :returns: True is this is a generator function, False otherwise. :rtype: bool """ - return next(self._get_yield_nodes_skip_lambdas(), False) + return bool(next(self._get_yield_nodes_skip_lambdas(), False)) def infer_call_result(self, caller=None, context=None): """Infer what the function returns when called. @@ -1682,7 +1723,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): except exceptions.InferenceError: yield util.Uninferable - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. @@ -1933,7 +1974,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement :type doc: str or None """ - super(ClassDef, self).__init__(lineno, col_offset, parent) + super().__init__(lineno, col_offset, parent) if parent is not None: parent.frame().set_local(name, self) @@ -2092,7 +2133,14 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement # Get the bases of the class. class_bases = next(caller.args[1].infer(context)) if isinstance(class_bases, (node_classes.Tuple, node_classes.List)): - result.bases = class_bases.itered() + bases = [] + for base in class_bases.itered(): + inferred = next(base.infer(context=context)) + if inferred: + bases.append( + node_classes.EvaluatedObject(original=base, value=inferred) + ) + result.bases = bases else: # There is currently no AST node that can represent an 'unknown' # node (Uninferable is not an AST node), therefore we simply return Uninferable here @@ -2381,6 +2429,11 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement :raises AttributeInferenceError: If the attribute cannot be inferred. """ + if not name: + raise exceptions.AttributeInferenceError( + target=self, attribute=name, context=context + ) + values = self.locals.get(name, []) if name in self.special_attributes and class_context and not values: result = [self.special_attributes.lookup(name)] @@ -2417,7 +2470,8 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement """Search the given name in the implicit and the explicit metaclass.""" attrs = set() implicit_meta = self.implicit_metaclass() - metaclass = self.metaclass() + context = contextmod.copy_context(context) + metaclass = self.metaclass(context=context) for cls in {implicit_meta, metaclass}: if cls and cls != self and isinstance(cls, ClassDef): cls_attributes = self._get_attribute_from_metaclass(cls, name, context) @@ -2467,8 +2521,20 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement metaclass = self.declared_metaclass(context=context) try: - attr = self.getattr(name, context, class_context=class_context)[0] - for inferred in bases._infer_stmts([attr], context, frame=self): + attributes = self.getattr(name, context, class_context=class_context) + # If we have more than one attribute, make sure that those starting from + # the second one are from the same scope. This is to account for modifications + # to the attribute happening *after* the attribute's definition (e.g. AugAssigns on lists) + if len(attributes) > 1: + first_attr, attributes = attributes[0], attributes[1:] + first_scope = first_attr.scope() + attributes = [first_attr] + [ + attr + for attr in attributes + if attr.parent and attr.parent.scope() == first_scope + ] + + for inferred in bases._infer_stmts(attributes, context, frame=self): # yield Uninferable object instead of descriptors when necessary if not isinstance(inferred, node_classes.Const) and isinstance( inferred, bases.Instance @@ -2768,7 +2834,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement if not all(slot is not None for slot in slots): return None - return sorted(slots, key=lambda item: item.value) + return sorted(set(slots), key=lambda item: item.value) def _inferred_bases(self, context=None): # Similar with .ancestors, but the difference is when one base is inferred, @@ -2837,7 +2903,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement """ return self._compute_mro(context=context) - def bool_value(self): + def bool_value(self, context=None): """Determine the boolean value of this node. :returns: The boolean value of this node. diff --git a/astroid/test_utils.py b/astroid/test_utils.py index 6c965efd..e22c7a4f 100644 --- a/astroid/test_utils.py +++ b/astroid/test_utils.py @@ -1,6 +1,6 @@ # Copyright (c) 2013-2014 Google, Inc. # Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> -# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> @@ -1,10 +1,11 @@ #!/usr/bin/env python # Copyright (c) 2006, 2009-2010, 2012-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2010-2011 Julien Jehannet <julien.jehannet@logilab.fr> -# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com> -# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2018-2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Enji Cooper <yaneurabeya@gmail.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/master/COPYING.LESSER @@ -28,8 +29,9 @@ with open(os.path.join(astroid_dir, "README.rst")) as fobj: long_description = fobj.read() -needs_pytest = set(['pytest', 'test', 'ptr']).intersection(sys.argv) -pytest_runner = ['pytest-runner'] if needs_pytest else [] +needs_pytest = set(["pytest", "test", "ptr"]).intersection(sys.argv) +pytest_runner = ["pytest-runner"] if needs_pytest else [] + def install(): return setup( @@ -42,10 +44,10 @@ def install(): author=author, author_email=author_email, url=web, - python_requires=">=3.5.*", + python_requires=">=3.5", install_requires=install_requires, extras_require=extras_require, - packages=find_packages() + ["astroid.brain"], + packages=find_packages(exclude=["tests"]) + ["astroid.brain"], setup_requires=pytest_runner, test_suite="test", tests_require=["pytest"], diff --git a/tests/resources.py b/tests/resources.py index ee57b9fe..7113f2b1 100644 --- a/tests/resources.py +++ b/tests/resources.py @@ -1,7 +1,8 @@ # Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -14,7 +15,7 @@ from astroid import MANAGER from astroid.bases import BUILTINS -DATA_DIR = os.path.join("testdata", "python{}".format(sys.version_info[0])) +DATA_DIR = os.path.join("testdata", "python3") RESOURCE_PATH = os.path.join(os.path.dirname(__file__), DATA_DIR, "data") diff --git a/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg b/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg Binary files differdeleted file mode 100644 index f62599c7..00000000 --- a/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg +++ /dev/null diff --git a/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.zip b/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.zip Binary files differdeleted file mode 100644 index f62599c7..00000000 --- a/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.zip +++ /dev/null diff --git a/tests/testdata/python2/data/SSL1/Connection1.py b/tests/testdata/python2/data/SSL1/Connection1.py deleted file mode 100644 index 1307b239..00000000 --- a/tests/testdata/python2/data/SSL1/Connection1.py +++ /dev/null @@ -1,5 +0,0 @@ - -class Connection: - - def __init__(self, ctx, sock=None): - print('init Connection') diff --git a/tests/testdata/python2/data/SSL1/__init__.py b/tests/testdata/python2/data/SSL1/__init__.py deleted file mode 100644 index a007b049..00000000 --- a/tests/testdata/python2/data/SSL1/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from Connection1 import Connection diff --git a/tests/testdata/python2/data/__init__.py b/tests/testdata/python2/data/__init__.py deleted file mode 100644 index 332e2e72..00000000 --- a/tests/testdata/python2/data/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__revision__="$Id: __init__.py,v 1.1 2005-06-13 20:55:20 syt Exp $" diff --git a/tests/testdata/python2/data/absimp/__init__.py b/tests/testdata/python2/data/absimp/__init__.py deleted file mode 100644 index b98444df..00000000 --- a/tests/testdata/python2/data/absimp/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""a package with absolute import activated -""" - -from __future__ import absolute_import - diff --git a/tests/testdata/python2/data/absimp/sidepackage/__init__.py b/tests/testdata/python2/data/absimp/sidepackage/__init__.py deleted file mode 100644 index 239499a6..00000000 --- a/tests/testdata/python2/data/absimp/sidepackage/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""a side package with nothing in it -""" - diff --git a/tests/testdata/python2/data/absimp/string.py b/tests/testdata/python2/data/absimp/string.py deleted file mode 100644 index e68e7496..00000000 --- a/tests/testdata/python2/data/absimp/string.py +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import absolute_import, print_function -import string -print(string) diff --git a/tests/testdata/python2/data/absimport.py b/tests/testdata/python2/data/absimport.py deleted file mode 100644 index f98effa6..00000000 --- a/tests/testdata/python2/data/absimport.py +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import absolute_import -import email -from email import message diff --git a/tests/testdata/python2/data/all.py b/tests/testdata/python2/data/all.py deleted file mode 100644 index 23f7d2b6..00000000 --- a/tests/testdata/python2/data/all.py +++ /dev/null @@ -1,9 +0,0 @@ - -name = 'a' -_bla = 2 -other = 'o' -class Aaa: pass - -def func(): print 'yo' - -__all__ = 'Aaa', '_bla', 'name' diff --git a/tests/testdata/python2/data/appl/__init__.py b/tests/testdata/python2/data/appl/__init__.py deleted file mode 100644 index d652ffd9..00000000 --- a/tests/testdata/python2/data/appl/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Init -""" diff --git a/tests/testdata/python2/data/appl/myConnection.py b/tests/testdata/python2/data/appl/myConnection.py deleted file mode 100644 index 5b24b259..00000000 --- a/tests/testdata/python2/data/appl/myConnection.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import print_function -from data import SSL1 -class MyConnection(SSL1.Connection): - - """An SSL connection.""" - - def __init__(self, dummy): - print('MyConnection init') - -if __name__ == '__main__': - myConnection = MyConnection(' ') - raw_input('Press Enter to continue...') diff --git a/tests/testdata/python2/data/contribute_to_namespace/namespace_pep_420/submodule.py b/tests/testdata/python2/data/contribute_to_namespace/namespace_pep_420/submodule.py deleted file mode 100644 index 6fbcff41..00000000 --- a/tests/testdata/python2/data/contribute_to_namespace/namespace_pep_420/submodule.py +++ /dev/null @@ -1 +0,0 @@ -var = 42
\ No newline at end of file diff --git a/tests/testdata/python2/data/descriptor_crash.py b/tests/testdata/python2/data/descriptor_crash.py deleted file mode 100644 index 11fbb4a2..00000000 --- a/tests/testdata/python2/data/descriptor_crash.py +++ /dev/null @@ -1,11 +0,0 @@ - -import urllib - -class Page(object): - _urlOpen = staticmethod(urllib.urlopen) - - def getPage(self, url): - handle = self._urlOpen(url) - data = handle.read() - handle.close() - return data diff --git a/tests/testdata/python2/data/email.py b/tests/testdata/python2/data/email.py deleted file mode 100644 index dc593564..00000000 --- a/tests/testdata/python2/data/email.py +++ /dev/null @@ -1 +0,0 @@ -"""fake email module to test absolute import doesn't grab this one""" diff --git a/tests/testdata/python2/data/find_test/module.py b/tests/testdata/python2/data/find_test/module.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/find_test/module.py +++ /dev/null diff --git a/tests/testdata/python2/data/find_test/module2.py b/tests/testdata/python2/data/find_test/module2.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/find_test/module2.py +++ /dev/null diff --git a/tests/testdata/python2/data/find_test/noendingnewline.py b/tests/testdata/python2/data/find_test/noendingnewline.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/find_test/noendingnewline.py +++ /dev/null diff --git a/tests/testdata/python2/data/find_test/nonregr.py b/tests/testdata/python2/data/find_test/nonregr.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/find_test/nonregr.py +++ /dev/null diff --git a/tests/testdata/python2/data/foogle/fax/__init__.py b/tests/testdata/python2/data/foogle/fax/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/foogle/fax/__init__.py +++ /dev/null diff --git a/tests/testdata/python2/data/foogle/fax/a.py b/tests/testdata/python2/data/foogle/fax/a.py deleted file mode 100644 index 3d2b4b14..00000000 --- a/tests/testdata/python2/data/foogle/fax/a.py +++ /dev/null @@ -1 +0,0 @@ -x = 1
\ No newline at end of file diff --git a/tests/testdata/python2/data/foogle_fax-0.12.5-py2.7-nspkg.pth b/tests/testdata/python2/data/foogle_fax-0.12.5-py2.7-nspkg.pth deleted file mode 100644 index eeb7ecac..00000000 --- a/tests/testdata/python2/data/foogle_fax-0.12.5-py2.7-nspkg.pth +++ /dev/null @@ -1,2 +0,0 @@ -import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('foogle',));ie = os.path.exists(os.path.join(p,'__init__.py'));m = not ie and sys.modules.setdefault('foogle', types.ModuleType('foogle'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p) -import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('foogle','crank'));ie = os.path.exists(os.path.join(p,'__init__.py'));m = not ie and sys.modules.setdefault('foogle.crank', types.ModuleType('foogle.crank'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p) diff --git a/tests/testdata/python2/data/format.py b/tests/testdata/python2/data/format.py deleted file mode 100644 index 73797061..00000000 --- a/tests/testdata/python2/data/format.py +++ /dev/null @@ -1,34 +0,0 @@ -"""A multiline string -""" - -function('aeozrijz\ -earzer', hop) -# XXX write test -x = [i for i in range(5) - if i % 4] - -fonction(1, - 2, - 3, - 4) - -def definition(a, - b, - c): - return a + b + c - -class debile(dict, - object): - pass - -if aaaa: pass -else: - aaaa,bbbb = 1,2 - aaaa,bbbb = bbbb,aaaa -# XXX write test -hop = \ - aaaa - - -__revision__.lower(); - diff --git a/tests/testdata/python2/data/invalid_encoding.py b/tests/testdata/python2/data/invalid_encoding.py deleted file mode 100644 index dddd208e..00000000 --- a/tests/testdata/python2/data/invalid_encoding.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: lala -*-
\ No newline at end of file diff --git a/tests/testdata/python2/data/lmfp/__init__.py b/tests/testdata/python2/data/lmfp/__init__.py deleted file mode 100644 index 74b26b82..00000000 --- a/tests/testdata/python2/data/lmfp/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# force a "direct" python import -from . import foo diff --git a/tests/testdata/python2/data/lmfp/foo.py b/tests/testdata/python2/data/lmfp/foo.py deleted file mode 100644 index 8f7de1e8..00000000 --- a/tests/testdata/python2/data/lmfp/foo.py +++ /dev/null @@ -1,6 +0,0 @@ -import sys -if not getattr(sys, 'bar', None): - sys.just_once = [] -# there used to be two numbers here because -# of a load_module_from_path bug -sys.just_once.append(42) diff --git a/tests/testdata/python2/data/module.py b/tests/testdata/python2/data/module.py deleted file mode 100644 index 1205c1dd..00000000 --- a/tests/testdata/python2/data/module.py +++ /dev/null @@ -1,90 +0,0 @@ -"""test module for astroid -""" - -__revision__ = '$Id: module.py,v 1.2 2005-11-02 11:56:54 syt Exp $' -from astroid.node_classes import Name as NameNode -from astroid import modutils -from astroid.utils import * -import os.path -MY_DICT = {} - -def global_access(key, val): - """function test""" - local = 1 - MY_DICT[key] = val - for i in val: - if i: - del MY_DICT[i] - continue - else: - break - else: - return local - - -class YO: - """hehe - haha""" - a = 1 - - def __init__(self): - try: - self.yo = 1 - except ValueError, ex: - pass - except (NameError, TypeError): - raise XXXError() - except: - raise - - - -class YOUPI(YO): - class_attr = None - - def __init__(self): - self.member = None - - def method(self): - """method - test""" - global MY_DICT - try: - MY_DICT = {} - local = None - autre = [a for (a, b) in MY_DICT if b] - if b in autre: - return b - elif a in autre: - return a - global_access(local, val=autre) - finally: - return local - - def static_method(): - """static method test""" - assert MY_DICT, '???' - static_method = staticmethod(static_method) - - def class_method(cls): - """class method test""" - exec a in b - class_method = classmethod(class_method) - - -def four_args(a, b, c, d): - """four arguments (was nested_args)""" - pass - while 1: - if a: - break - a += +1 - else: - b += -2 - if c: - d = a and (b or c) - else: - c = a and b or d - map(lambda x, y: (y, x), a) -redirect = four_args - diff --git a/tests/testdata/python2/data/module1abs/__init__.py b/tests/testdata/python2/data/module1abs/__init__.py deleted file mode 100644 index 42949a44..00000000 --- a/tests/testdata/python2/data/module1abs/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import absolute_import, print_function -from . import core -from .core import * -print(sys.version) diff --git a/tests/testdata/python2/data/module1abs/core.py b/tests/testdata/python2/data/module1abs/core.py deleted file mode 100644 index de101117..00000000 --- a/tests/testdata/python2/data/module1abs/core.py +++ /dev/null @@ -1 +0,0 @@ -import sys diff --git a/tests/testdata/python2/data/module2.py b/tests/testdata/python2/data/module2.py deleted file mode 100644 index 7042a4c1..00000000 --- a/tests/testdata/python2/data/module2.py +++ /dev/null @@ -1,143 +0,0 @@ -from data.module import YO, YOUPI -import data - - -class Specialization(YOUPI, YO): - pass - - - -class Metaclass(type): - pass - - - -class Interface: - pass - - - -class MyIFace(Interface): - pass - - - -class AnotherIFace(Interface): - pass - - - -class MyException(Exception): - pass - - - -class MyError(MyException): - pass - - - -class AbstractClass(object): - - def to_override(self, whatever): - raise NotImplementedError() - - def return_something(self, param): - if param: - return 'toto' - return - - - -class Concrete0: - __implements__ = MyIFace - - - -class Concrete1: - __implements__ = (MyIFace, AnotherIFace) - - - -class Concrete2: - __implements__ = (MyIFace, AnotherIFace) - - - -class Concrete23(Concrete1): - pass - -del YO.member -del YO -[SYN1, SYN2] = (Concrete0, Concrete1) -assert '1' -b = (1 | 2) & (3 ^ 8) -bb = 1 | (two | 6) -ccc = one & two & three -dddd = x ^ (o ^ r) -exec 'c = 3' -exec 'c = 3' in {}, {} - -def raise_string(a=2, *args, **kwargs): - raise Exception, 'yo' - yield 'coucou' - yield -a = b + 2 -c = b * 2 -c = b / 2 -c = b // 2 -c = b - 2 -c = b % 2 -c = b**2 -c = b << 2 -c = b >> 2 -c = ~b -c = not b -d = [c] -e = d[:] -e = d[a:b:c] -raise_string(*args, **kwargs) -print >> stream, 'bonjour' -print >> stream, 'salut', - -def make_class(any, base=data.module.YO, *args, **kwargs): - """check base is correctly resolved to Concrete0""" - - - class Aaaa(base): - """dynamic class""" - - - return Aaaa -from os.path import abspath -import os as myos - - -class A: - pass - - - -class A(A): - pass - - -def generator(): - """A generator.""" - yield - -def not_a_generator(): - """A function that contains generator, but is not one.""" - - def generator(): - yield - genl = lambda: (yield) - -def with_metaclass(meta, *bases): - return meta('NewBase', bases, {}) - - -class NotMetaclass(with_metaclass(Metaclass)): - pass - - diff --git a/tests/testdata/python2/data/namespace_pep_420/module.py b/tests/testdata/python2/data/namespace_pep_420/module.py deleted file mode 100644 index a4d111e6..00000000 --- a/tests/testdata/python2/data/namespace_pep_420/module.py +++ /dev/null @@ -1 +0,0 @@ -from namespace_pep_420.submodule import var
\ No newline at end of file diff --git a/tests/testdata/python2/data/noendingnewline.py b/tests/testdata/python2/data/noendingnewline.py deleted file mode 100644 index e1d6e4a1..00000000 --- a/tests/testdata/python2/data/noendingnewline.py +++ /dev/null @@ -1,36 +0,0 @@ -import unittest - - -class TestCase(unittest.TestCase): - - def setUp(self): - unittest.TestCase.setUp(self) - - - def tearDown(self): - unittest.TestCase.tearDown(self) - - def testIt(self): - self.a = 10 - self.xxx() - - - def xxx(self): - if False: - pass - print 'a' - - if False: - pass - pass - - if False: - pass - print 'rara' - - -if __name__ == '__main__': - print 'test2' - unittest.main() - - diff --git a/tests/testdata/python2/data/nonregr.py b/tests/testdata/python2/data/nonregr.py deleted file mode 100644 index 813469fe..00000000 --- a/tests/testdata/python2/data/nonregr.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import generators, print_function - -try: - enumerate = enumerate -except NameError: - - def enumerate(iterable): - """emulates the python2.3 enumerate() function""" - i = 0 - for val in iterable: - yield i, val - i += 1 - -def toto(value): - for k, v in value: - print(v.get('yo')) - - -import imp -fp, mpath, desc = imp.find_module('optparse',a) -s_opt = imp.load_module('std_optparse', fp, mpath, desc) - -class OptionParser(s_opt.OptionParser): - - def parse_args(self, args=None, values=None, real_optparse=False): - if real_optparse: - pass -## return super(OptionParser, self).parse_args() - else: - import optcomp - optcomp.completion(self) - - -class Aaa(object): - """docstring""" - def __init__(self): - self.__setattr__('a','b') - pass - - def one_public(self): - """docstring""" - pass - - def another_public(self): - """docstring""" - pass - -class Ccc(Aaa): - """docstring""" - - class Ddd(Aaa): - """docstring""" - pass - - class Eee(Ddd): - """docstring""" - pass diff --git a/tests/testdata/python2/data/notall.py b/tests/testdata/python2/data/notall.py deleted file mode 100644 index 042491e0..00000000 --- a/tests/testdata/python2/data/notall.py +++ /dev/null @@ -1,7 +0,0 @@ -name = 'a' -_bla = 2 -other = 'o' -class Aaa: pass - -def func(): return 'yo' - diff --git a/tests/testdata/python2/data/notamodule/file.py b/tests/testdata/python2/data/notamodule/file.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/notamodule/file.py +++ /dev/null diff --git a/tests/testdata/python2/data/operator_precedence.py b/tests/testdata/python2/data/operator_precedence.py deleted file mode 100644 index 07a3c772..00000000 --- a/tests/testdata/python2/data/operator_precedence.py +++ /dev/null @@ -1,27 +0,0 @@ -assert not not True == True -assert (not False or True) == True -assert True or False and True -assert (True or False) and True - -assert True is not (False is True) == False -assert True is (not False is True == False) - -assert 1 + 2 + 3 == 6 -assert 5 - 4 + 3 == 4 -assert 4 - 5 - 6 == -7 -assert 7 - (8 - 9) == 8 -assert 2**3**4 == 2**81 -assert (2**3)**4 == 8**4 - -assert 1 + 2 if (0.5 if True else 0.2) else 1 if True else 2 == 3 -assert (0 if True else 1) if False else 2 == 2 -assert lambda x: x if (0 if False else 0) else 0 if False else 0 -assert (lambda x: x) if (0 if True else 0.2) else 1 if True else 2 == 1 - -assert ('1' + '2').replace('1', '3') == '32' -assert (lambda x: x)(1) == 1 -assert ([0] + [1])[1] == 1 -assert (lambda x: lambda: x + 1)(2)() == 3 - -f = lambda x, y, z: y(x, z) -assert f(1, lambda x, y: x + y[1], (2, 3)) == 4 diff --git a/tests/testdata/python2/data/package/__init__.py b/tests/testdata/python2/data/package/__init__.py deleted file mode 100644 index 575d18b1..00000000 --- a/tests/testdata/python2/data/package/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""package's __init__ file""" - - -from . import subpackage diff --git a/tests/testdata/python2/data/package/absimport.py b/tests/testdata/python2/data/package/absimport.py deleted file mode 100644 index 33ed117c..00000000 --- a/tests/testdata/python2/data/package/absimport.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import absolute_import, print_function -import import_package_subpackage_module # fail -print(import_package_subpackage_module) - -from . import hello as hola - diff --git a/tests/testdata/python2/data/package/hello.py b/tests/testdata/python2/data/package/hello.py deleted file mode 100644 index b154c844..00000000 --- a/tests/testdata/python2/data/package/hello.py +++ /dev/null @@ -1,2 +0,0 @@ -"""hello module""" - diff --git a/tests/testdata/python2/data/package/import_package_subpackage_module.py b/tests/testdata/python2/data/package/import_package_subpackage_module.py deleted file mode 100644 index ad442c16..00000000 --- a/tests/testdata/python2/data/package/import_package_subpackage_module.py +++ /dev/null @@ -1,49 +0,0 @@ -# pylint: disable-msg=I0011,C0301,W0611 -"""I found some of my scripts trigger off an AttributeError in pylint -0.8.1 (with common 0.12.0 and astroid 0.13.1). - -Traceback (most recent call last): - File "/usr/bin/pylint", line 4, in ? - lint.Run(sys.argv[1:]) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 729, in __init__ - linter.check(args) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 412, in check - self.check_file(filepath, modname, checkers) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 426, in check_file - astroid = self._check_file(filepath, modname, checkers) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file - self.check_astroid_module(astroid, checkers) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astroid_module - self.astroid_events(astroid, [checker for checker in checkers - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events - self.astroid_events(child, checkers, _reversed_checkers) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events - self.astroid_events(child, checkers, _reversed_checkers) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astroid_events - checker.visit(astroid) - File "/usr/lib/python2.4/site-packages/logilab/astroid/utils.py", line 84, in visit - method(node) - File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 295, in visit_import - self._check_module_attrs(node, module, name_parts[1:]) - File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 357, in _check_module_attrs - self.add_message('E0611', args=(name, module.name), -AttributeError: Import instance has no attribute 'name' - - -You can reproduce it by: -(1) create package structure like the following: - -package/ - __init__.py - subpackage/ - __init__.py - module.py - -(2) in package/__init__.py write: - -import subpackage - -(3) run pylint with a script importing package.subpackage.module. -""" -__revision__ = '$Id: import_package_subpackage_module.py,v 1.1 2005-11-10 15:59:32 syt Exp $' -import package.subpackage.module diff --git a/tests/testdata/python2/data/package/subpackage/__init__.py b/tests/testdata/python2/data/package/subpackage/__init__.py deleted file mode 100644 index dc4782e6..00000000 --- a/tests/testdata/python2/data/package/subpackage/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""package.subpackage""" diff --git a/tests/testdata/python2/data/package/subpackage/module.py b/tests/testdata/python2/data/package/subpackage/module.py deleted file mode 100644 index 4b7244ba..00000000 --- a/tests/testdata/python2/data/package/subpackage/module.py +++ /dev/null @@ -1 +0,0 @@ -"""package.subpackage.module""" diff --git a/tests/testdata/python2/data/path_pkg_resources_1/package/__init__.py b/tests/testdata/python2/data/path_pkg_resources_1/package/__init__.py deleted file mode 100644 index b0d64337..00000000 --- a/tests/testdata/python2/data/path_pkg_resources_1/package/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__)
\ No newline at end of file diff --git a/tests/testdata/python2/data/path_pkg_resources_1/package/foo.py b/tests/testdata/python2/data/path_pkg_resources_1/package/foo.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/path_pkg_resources_1/package/foo.py +++ /dev/null diff --git a/tests/testdata/python2/data/path_pkg_resources_2/package/__init__.py b/tests/testdata/python2/data/path_pkg_resources_2/package/__init__.py deleted file mode 100644 index b0d64337..00000000 --- a/tests/testdata/python2/data/path_pkg_resources_2/package/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__)
\ No newline at end of file diff --git a/tests/testdata/python2/data/path_pkg_resources_2/package/bar.py b/tests/testdata/python2/data/path_pkg_resources_2/package/bar.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/path_pkg_resources_2/package/bar.py +++ /dev/null diff --git a/tests/testdata/python2/data/path_pkg_resources_3/package/__init__.py b/tests/testdata/python2/data/path_pkg_resources_3/package/__init__.py deleted file mode 100644 index b0d64337..00000000 --- a/tests/testdata/python2/data/path_pkg_resources_3/package/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__)
\ No newline at end of file diff --git a/tests/testdata/python2/data/path_pkg_resources_3/package/baz.py b/tests/testdata/python2/data/path_pkg_resources_3/package/baz.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/path_pkg_resources_3/package/baz.py +++ /dev/null diff --git a/tests/testdata/python2/data/path_pkgutil_1/package/__init__.py b/tests/testdata/python2/data/path_pkgutil_1/package/__init__.py deleted file mode 100644 index 0bfb5a62..00000000 --- a/tests/testdata/python2/data/path_pkgutil_1/package/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__)
\ No newline at end of file diff --git a/tests/testdata/python2/data/path_pkgutil_1/package/foo.py b/tests/testdata/python2/data/path_pkgutil_1/package/foo.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/path_pkgutil_1/package/foo.py +++ /dev/null diff --git a/tests/testdata/python2/data/path_pkgutil_2/package/__init__.py b/tests/testdata/python2/data/path_pkgutil_2/package/__init__.py deleted file mode 100644 index 0bfb5a62..00000000 --- a/tests/testdata/python2/data/path_pkgutil_2/package/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__)
\ No newline at end of file diff --git a/tests/testdata/python2/data/path_pkgutil_2/package/bar.py b/tests/testdata/python2/data/path_pkgutil_2/package/bar.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/path_pkgutil_2/package/bar.py +++ /dev/null diff --git a/tests/testdata/python2/data/path_pkgutil_3/package/__init__.py b/tests/testdata/python2/data/path_pkgutil_3/package/__init__.py deleted file mode 100644 index 0bfb5a62..00000000 --- a/tests/testdata/python2/data/path_pkgutil_3/package/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__)
\ No newline at end of file diff --git a/tests/testdata/python2/data/path_pkgutil_3/package/baz.py b/tests/testdata/python2/data/path_pkgutil_3/package/baz.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/path_pkgutil_3/package/baz.py +++ /dev/null diff --git a/tests/testdata/python2/data/recursion.py b/tests/testdata/python2/data/recursion.py deleted file mode 100644 index a34dad32..00000000 --- a/tests/testdata/python2/data/recursion.py +++ /dev/null @@ -1,3 +0,0 @@ -""" For issue #25 """
-class Base(object):
- pass
\ No newline at end of file diff --git a/tests/testdata/python2/data/tmp__init__.py b/tests/testdata/python2/data/tmp__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/tmp__init__.py +++ /dev/null diff --git a/tests/testdata/python2/data/unicode_package/__init__.py b/tests/testdata/python2/data/unicode_package/__init__.py deleted file mode 100644 index 713e5591..00000000 --- a/tests/testdata/python2/data/unicode_package/__init__.py +++ /dev/null @@ -1 +0,0 @@ -x = "șțîâ"
\ No newline at end of file diff --git a/tests/testdata/python2/data/unicode_package/core/__init__.py b/tests/testdata/python2/data/unicode_package/core/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/tests/testdata/python2/data/unicode_package/core/__init__.py +++ /dev/null diff --git a/tests/testdata/python2/data/find_test/__init__.py b/tests/testdata/python3/data/metaclass_recursion/__init__.py index e69de29b..e69de29b 100644 --- a/tests/testdata/python2/data/find_test/__init__.py +++ b/tests/testdata/python3/data/metaclass_recursion/__init__.py diff --git a/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py b/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py new file mode 100644 index 00000000..757bb3f8 --- /dev/null +++ b/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py @@ -0,0 +1,17 @@ +# https://github.com/PyCQA/astroid/issues/749 +# Not an actual module but allows us to reproduce the issue +from tests.testdata.python3.data.metaclass_recursion import parent + +class MonkeyPatchClass(parent.OriginalClass): + _original_class = parent.OriginalClass + + @classmethod + def patch(cls): + if parent.OriginalClass != MonkeyPatchClass: + cls._original_class = parent.OriginalClass + parent.OriginalClass = MonkeyPatchClass + + @classmethod + def unpatch(cls): + if parent.OriginalClass == MonkeyPatchClass: + parent.OriginalClass = cls._original_class diff --git a/tests/testdata/python3/data/metaclass_recursion/parent.py b/tests/testdata/python3/data/metaclass_recursion/parent.py new file mode 100644 index 00000000..5cff73e0 --- /dev/null +++ b/tests/testdata/python3/data/metaclass_recursion/parent.py @@ -0,0 +1,3 @@ +# https://github.com/PyCQA/astroid/issues/749 +class OriginalClass: + pass diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py index 1a082d12..0a833664 100644 --- a/tests/unittest_brain.py +++ b/tests/unittest_brain.py @@ -1,20 +1,28 @@ # -*- coding: utf-8 -*- # Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# 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 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com> -# Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@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 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 Nick Drozd <nicholasdrozd@gmail.com> # 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 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) 2019 Bryce Guinta <bryce.guinta@protonmail.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/master/COPYING.LESSER @@ -323,6 +331,18 @@ class NamedTupleTest(unittest.TestCase): self.assertIsInstance(inferred.bases[0], astroid.Name) self.assertEqual(inferred.bases[0].name, "tuple") + def test_invalid_label_does_not_crash_inference(self): + 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 + class DefaultDictTest(unittest.TestCase): def test_1(self): @@ -622,6 +642,21 @@ class EnumBrainTest(unittest.TestCase): test = next(enumeration.igetattr("test")) self.assertEqual(test.value, 42) + def test_ignores_with_nodes_from_body_of_enum(self): + 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): module = builder.parse( """ @@ -1633,10 +1668,13 @@ class TestLenBuiltinInference: ) 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""" + """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): @@ -1648,7 +1686,7 @@ class TestLenBuiltinInference: len(F()) """ ) - assert next(node.infer()).as_string() == "5" + assert next(node.infer()).as_string() == "0" @pytest.mark.xfail(reason="Can't use list special astroid fields") def test_int_subclass_argument(self): diff --git a/tests/unittest_brain_numpy_core_fromnumeric.py b/tests/unittest_brain_numpy_core_fromnumeric.py index 16d73e2e..fd571f30 100644 --- a/tests/unittest_brain_numpy_core_fromnumeric.py +++ b/tests/unittest_brain_numpy_core_fromnumeric.py @@ -1,5 +1,6 @@ # -*- encoding=utf-8 -*- -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER diff --git a/tests/unittest_brain_numpy_core_function_base.py b/tests/unittest_brain_numpy_core_function_base.py index 06a940c8..109238a2 100644 --- a/tests/unittest_brain_numpy_core_function_base.py +++ b/tests/unittest_brain_numpy_core_function_base.py @@ -1,5 +1,6 @@ # -*- encoding=utf-8 -*- -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER diff --git a/tests/unittest_brain_numpy_core_multiarray.py b/tests/unittest_brain_numpy_core_multiarray.py index e02db6cc..9e945d21 100644 --- a/tests/unittest_brain_numpy_core_multiarray.py +++ b/tests/unittest_brain_numpy_core_multiarray.py @@ -1,5 +1,6 @@ # -*- encoding=utf-8 -*- -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER diff --git a/tests/unittest_brain_numpy_core_numeric.py b/tests/unittest_brain_numpy_core_numeric.py index 56b7d0d6..a39fb19e 100644 --- a/tests/unittest_brain_numpy_core_numeric.py +++ b/tests/unittest_brain_numpy_core_numeric.py @@ -1,5 +1,6 @@ # -*- encoding=utf-8 -*- -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER diff --git a/tests/unittest_brain_numpy_core_numerictypes.py b/tests/unittest_brain_numpy_core_numerictypes.py index 768a5570..db4fdd23 100644 --- a/tests/unittest_brain_numpy_core_numerictypes.py +++ b/tests/unittest_brain_numpy_core_numerictypes.py @@ -1,7 +1,8 @@ # -*- encoding=utf-8 -*- -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> -# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2017-2020 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -314,6 +315,34 @@ class NumpyBrainCoreNumericTypesTest(unittest.TestCase): with self.subTest(attr=attr): self.assertNotEqual(len(inferred.getattr(attr)), 0) + def test_datetime_astype_return(self): + """ + Test that the return of astype method of the datetime object + is inferred as a ndarray. + + PyCQA/pylint#3332 + """ + node = builder.extract_node( + """ + import numpy as np + import datetime + test_array = np.datetime64(1, 'us') + test_array.astype(datetime.datetime) + """ + ) + licit_array_types = ".ndarray" + inferred_values = list(node.infer()) + self.assertTrue( + len(inferred_values) == 1, + msg="Too much inferred value for {:s}".format("datetime64.astype"), + ) + self.assertTrue( + inferred_values[-1].pytype() in licit_array_types, + msg="Illicit type for {:s} ({})".format( + "datetime64.astype", inferred_values[-1].pytype() + ), + ) + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_brain_numpy_core_umath.py b/tests/unittest_brain_numpy_core_umath.py index e6fb1c5e..3db3f149 100644 --- a/tests/unittest_brain_numpy_core_umath.py +++ b/tests/unittest_brain_numpy_core_umath.py @@ -1,5 +1,6 @@ # -*- encoding=utf-8 -*- -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER diff --git a/tests/unittest_brain_numpy_ndarray.py b/tests/unittest_brain_numpy_ndarray.py index d982f7f6..defce47d 100644 --- a/tests/unittest_brain_numpy_ndarray.py +++ b/tests/unittest_brain_numpy_ndarray.py @@ -1,7 +1,8 @@ # -*- encoding=utf-8 -*- -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> -# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2017-2020 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -117,6 +118,18 @@ class NumpyBrainNdarrayTest(unittest.TestCase): ) return node.infer() + def _inferred_ndarray_attribute(self, attr_name): + node = builder.extract_node( + """ + import numpy as np + test_array = np.ndarray((2, 2)) + test_array.{:s} + """.format( + attr_name + ) + ) + return node.infer() + def test_numpy_function_calls_inferred_as_ndarray(self): """ Test that some calls to numpy functions are inferred as numpy.ndarray @@ -136,6 +149,25 @@ class NumpyBrainNdarrayTest(unittest.TestCase): ), ) + def test_numpy_ndarray_attribute_inferred_as_ndarray(self): + """ + Test that some numpy ndarray attributes are inferred as numpy.ndarray + """ + licit_array_types = ".ndarray" + for attr_ in ("real", "imag"): + with self.subTest(typ=attr_): + inferred_values = list(self._inferred_ndarray_attribute(attr_)) + self.assertTrue( + len(inferred_values) == 1, + msg="Too much inferred value for {:s}".format(attr_), + ) + self.assertTrue( + inferred_values[-1].pytype() in licit_array_types, + msg="Illicit type for {:s} ({})".format( + attr_, inferred_values[-1].pytype() + ), + ) + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_brain_numpy_random_mtrand.py b/tests/unittest_brain_numpy_random_mtrand.py index 4ca296f1..20a5d310 100644 --- a/tests/unittest_brain_numpy_random_mtrand.py +++ b/tests/unittest_brain_numpy_random_mtrand.py @@ -1,7 +1,6 @@ # -*- encoding=utf-8 -*- -# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> -# Copyright (c) 2017 Claudiu Popa <pcmanticore@gmail.com> -# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py index 9ea597c8..22e49e60 100644 --- a/tests/unittest_builder.py +++ b/tests/unittest_builder.py @@ -1,14 +1,17 @@ # -*- coding: utf-8 -*- # Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014-2015 Google, Inc. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2017 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER diff --git a/tests/unittest_helpers.py b/tests/unittest_helpers.py index 8ed07ede..ced5fc0a 100644 --- a/tests/unittest_helpers.py +++ b/tests/unittest_helpers.py @@ -1,5 +1,6 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -192,21 +193,6 @@ class TestHelpers(unittest.TestCase): self.assertFalse(helpers.is_subtype(cls_a, cls_b)) self.assertFalse(helpers.is_subtype(cls_a, cls_b)) - @test_utils.require_version(maxver="3.0") - def test_is_subtype_supertype_old_style_classes(self): - cls_a, cls_b = builder.extract_node( - """ - class A: #@ - pass - class B(A): #@ - pass - """ - ) - self.assertFalse(helpers.is_subtype(cls_a, cls_b)) - self.assertFalse(helpers.is_subtype(cls_b, cls_a)) - self.assertFalse(helpers.is_supertype(cls_a, cls_b)) - self.assertFalse(helpers.is_supertype(cls_b, cls_a)) - def test_is_subtype_supertype_mro_error(self): cls_e, cls_f = builder.extract_node( """ diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index b541eb28..b3df5f6c 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -2,7 +2,7 @@ # Copyright (c) 2006-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2007 Marien Zwart <marienz@gentoo.org> # Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru> @@ -14,8 +14,14 @@ # Copyright (c) 2017 Calen Pennington <calen.pennington@gmail.com> # Copyright (c) 2017 David Euresti <david@dropbox.com> # Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com> +# Copyright (c) 2018 Daniel Martin <daniel.martin@crowdstrike.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> +# Copyright (c) 2019 Stanislav Levin <slev@altlinux.org> +# Copyright (c) 2019 David Liu <david@cs.toronto.edu> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER @@ -24,6 +30,7 @@ """ # pylint: disable=too-many-lines import platform +import textwrap from functools import partial import unittest from unittest.mock import patch @@ -499,18 +506,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): self.assertEqual(inferred.name, "set") self.assertIn("remove", inferred._proxied.locals) - @test_utils.require_version(maxver="3.0") - def test_unicode_type(self): - code = '''u = u""''' - ast = parse(code, __name__) - n = ast["u"] - inferred = next(n.infer()) - self.assertIsInstance(inferred, nodes.Const) - self.assertIsInstance(inferred, Instance) - self.assertEqual(inferred.name, "unicode") - self.assertIn("lower", inferred._proxied.locals) - - @unittest.expectedFailure + @pytest.mark.xfail(reason="Descriptors are not properly inferred as callable") def test_descriptor_are_callable(self): code = """ class A: @@ -1701,7 +1697,9 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): pass """ ) - self.assertIn("object", [base.name for base in klass.ancestors()]) + ancestors = [base.name for base in klass.ancestors()] + expected_subset = ["datetime", "date"] + self.assertEqual(expected_subset, ancestors[:2]) def test_stop_iteration_leak(self): code = """ @@ -3256,7 +3254,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): self.assertIsInstance(inferred, Instance) self.assertEqual(inferred.name, "B") - @unittest.expectedFailure + @pytest.mark.xfail(reason="String interpolation is incorrect for modulo formatting") def test_string_interpolation(self): ast_nodes = extract_node( """ @@ -3628,7 +3626,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ] self.assertEqual(titles, ["Catch 22", "Ubik", "Grimus"]) - @unittest.expectedFailure + @pytest.mark.xfail(reason="Does not support function metaclasses") def test_function_metaclasses(self): # These are not supported right now, although # they will be in the future. @@ -3776,7 +3774,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): second = next(ast_nodes[1].infer()) self.assertIsInstance(second, Instance) - @unittest.expectedFailure + @pytest.mark.xfail(reason="Metaclass arguments not inferred as classes") def test_metaclass_arguments_are_classes_not_instances(self): ast_node = extract_node( """ @@ -3859,19 +3857,6 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): inferred.getattr("teta") inferred.getattr("a") - @test_utils.require_version(maxver="3.0") - def test_delayed_attributes_with_old_style_classes(self): - ast_node = extract_node( - """ - class A: - __slots__ = ('a', ) - a = A() - a.teta = 42 - a #@ - """ - ) - next(ast_node.infer()).getattr("teta") - def test_lambda_as_methods(self): ast_node = extract_node( """ @@ -3906,7 +3891,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): self.assertIsInstance(inferred, nodes.Const) self.assertEqual(inferred.value, 25) - @unittest.expectedFailure + @pytest.mark.xfail(reason="Cannot reuse inner value due to inference context reuse") def test_inner_value_redefined_by_subclass_with_mro(self): # This might work, but it currently doesn't due to not being able # to reuse inference contexts. @@ -5169,10 +5154,25 @@ def test_builtin_inference_list_of_exceptions(): inferred = next(node.infer()) assert isinstance(inferred, nodes.Tuple) assert len(inferred.elts) == 2 - assert isinstance(inferred.elts[0], nodes.ClassDef) - assert inferred.elts[0].name == "ValueError" - assert isinstance(inferred.elts[1], nodes.ClassDef) - assert inferred.elts[1].name == "TypeError" + assert isinstance(inferred.elts[0], nodes.EvaluatedObject) + assert isinstance(inferred.elts[0].value, nodes.ClassDef) + assert inferred.elts[0].value.name == "ValueError" + assert isinstance(inferred.elts[1], nodes.EvaluatedObject) + assert isinstance(inferred.elts[1].value, nodes.ClassDef) + assert inferred.elts[1].value.name == "TypeError" + + # Test that inference of evaluated objects returns what is expected + first_elem = next(inferred.elts[0].infer()) + assert isinstance(first_elem, nodes.ClassDef) + assert first_elem.name == "ValueError" + + second_elem = next(inferred.elts[1].infer()) + assert isinstance(second_elem, nodes.ClassDef) + assert second_elem.name == "TypeError" + + # Test that as_string() also works + as_string = inferred.as_string() + assert as_string.strip() == "(ValueError, TypeError)" @test_utils.require_version(minver="3.6") @@ -5478,6 +5478,28 @@ def test_property_inference(): assert isinstance(inferred, nodes.FunctionDef) +def test_property_as_string(): + code = """ + class A: + @property + def test(self): + return 42 + + A.test #@ + """ + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, objects.Property) + property_body = textwrap.dedent( + """ + @property + def test(self): + return 42 + """ + ) + assert inferred.as_string().strip() == property_body.strip() + + def test_property_callable_inference(): code = """ class A: @@ -5517,5 +5539,330 @@ def test_recursion_error_inferring_builtin_containers(): helpers.safe_infer(node.targets[0]) +def test_inferaugassign_picking_parent_instead_of_stmt(): + code = """ + from collections import namedtuple + SomeClass = namedtuple('SomeClass', ['name']) + items = [SomeClass(name='some name')] + + some_str = '' + some_str += ', '.join(__(item) for item in items) + """ + # item needs to be inferrd as `SomeClass` but it was inferred + # as a string because the entire `AugAssign` node was inferred + # as a string. + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, Instance) + assert inferred.name == "SomeClass" + + +def test_classmethod_from_builtins_inferred_as_bound(): + code = """ + import builtins + + class Foo(): + @classmethod + def bar1(cls, text): + pass + + @builtins.classmethod + def bar2(cls, text): + pass + + Foo.bar1 #@ + Foo.bar2 #@ + """ + first_node, second_node = extract_node(code) + assert isinstance(next(first_node.infer()), BoundMethod) + assert isinstance(next(second_node.infer()), BoundMethod) + + +def test_infer_dict_passes_context(): + code = """ + k = {} + (_ for k in __(dict(**k))) + """ + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, Instance) + assert inferred.qname() == "builtins.dict" + + +@pytest.mark.parametrize( + "code,obj,obj_type", + [ + ( + """ + def klassmethod1(method): + @classmethod + def inner(cls): + return method(cls) + return inner + + class X(object): + @klassmethod1 + def x(cls): + return 'X' + X.x + """, + BoundMethod, + "classmethod", + ), + ( + """ + def staticmethod1(method): + @staticmethod + def inner(cls): + return method(cls) + return inner + + class X(object): + @staticmethod1 + def x(cls): + return 'X' + X.x + """, + nodes.FunctionDef, + "staticmethod", + ), + ( + """ + def klassmethod1(method): + def inner(cls): + return method(cls) + return classmethod(inner) + + class X(object): + @klassmethod1 + def x(cls): + return 'X' + X.x + """, + BoundMethod, + "classmethod", + ), + ( + """ + def staticmethod1(method): + def inner(cls): + return method(cls) + return staticmethod(inner) + + class X(object): + @staticmethod1 + def x(cls): + return 'X' + X.x + """, + nodes.FunctionDef, + "staticmethod", + ), + ], +) +def test_custom_decorators_for_classmethod_and_staticmethods(code, obj, obj_type): + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, obj) + assert inferred.type == obj_type + + +@pytest.mark.skipif(sys.version_info < (3, 8), reason="Needs dataclasses available") +def test_dataclasses_subscript_inference_recursion_error(): + code = """ + from dataclasses import dataclass, replace + + @dataclass + class ProxyConfig: + auth: str = "/auth" + + + a = ProxyConfig("") + test_dict = {"proxy" : {"auth" : "", "bla" : "f"}} + + foo = test_dict['proxy'] + replace(a, **test_dict['proxy']) # This fails + """ + node = extract_node(code) + # Reproduces only with safe_infer() + assert helpers.safe_infer(node) is None + + +def test_self_reference_infer_does_not_trigger_recursion_error(): + # Prevents https://github.com/PyCQA/pylint/issues/1285 + code = """ + def func(elems): + return elems + + class BaseModel(object): + + def __init__(self, *args, **kwargs): + self._reference = func(*self._reference.split('.')) + BaseModel()._reference + """ + node = extract_node(code) + inferred = next(node.infer()) + assert inferred is util.Uninferable + + +def test_inferring_properties_multiple_time_does_not_mutate_locals_multiple_times(): + code = """ + class A: + @property + def a(self): + return 42 + + A() + """ + node = extract_node(code) + # Infer the class + cls = next(node.infer()) + prop, = cls.getattr("a") + + # Try to infer the property function *multiple* times. `A.locals` should be modified only once + for _ in range(3): + prop.inferred() + a_locals = cls.locals["a"] + # [FunctionDef, Property] + assert len(a_locals) == 2 + + +def test_getattr_fails_on_empty_values(): + code = """ + import collections + collections + """ + node = extract_node(code) + inferred = next(node.infer()) + with pytest.raises(exceptions.InferenceError): + next(inferred.igetattr("")) + + with pytest.raises(exceptions.AttributeInferenceError): + inferred.getattr("") + + +def test_infer_first_argument_of_static_method_in_metaclass(): + code = """ + class My(type): + @staticmethod + def test(args): + args #@ + """ + node = extract_node(code) + inferred = next(node.infer()) + assert inferred is util.Uninferable + + +def test_recursion_error_metaclass_monkeypatching(): + module = resources.build_file( + "data/metaclass_recursion/monkeypatch.py", "data.metaclass_recursion" + ) + cls = next(module.igetattr("MonkeyPatchClass")) + assert isinstance(cls, nodes.ClassDef) + assert cls.declared_metaclass() is None + + +@pytest.mark.xfail(reason="Cannot fully infer all the base classes properly.") +def test_recursion_error_self_reference_type_call(): + # Fix for https://github.com/PyCQA/astroid/issues/199 + code = """ + class A(object): + pass + class SomeClass(object): + route_class = A + def __init__(self): + self.route_class = type('B', (self.route_class, ), {}) + self.route_class() #@ + """ + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, Instance) + assert inferred.name == "B" + # TODO: Cannot infer [B, A, object] but at least the recursion error is gone. + assert [cls.name for cls in inferred.mro()] == ["B", "A", "object"] + + +def test_allow_retrieving_instance_attrs_and_special_attrs_for_functions(): + code = """ + class A: + def test(self): + "a" + # Add `__doc__` to `FunctionDef.instance_attrs` via an `AugAssign` + test.__doc__ += 'b' + test #@ + """ + node = extract_node(code) + inferred = next(node.infer()) + attrs = inferred.getattr("__doc__") + # One from the `AugAssign`, one from the special attributes + assert len(attrs) == 2 + + +def test_implicit_parameters_bound_method(): + code = """ + class A(type): + @classmethod + def test(cls, first): return first + def __new__(cls, name, bases, dictionary): + return super().__new__(cls, name, bases, dictionary) + + A.test #@ + A.__new__ #@ + """ + test, dunder_new = extract_node(code) + test = next(test.infer()) + assert isinstance(test, BoundMethod) + assert test.implicit_parameters() == 1 + + dunder_new = next(dunder_new.infer()) + assert isinstance(dunder_new, BoundMethod) + assert dunder_new.implicit_parameters() == 0 + + +def test_super_inference_of_abstract_property(): + code = """ + from abc import abstractmethod + + class A: + @property + def test(self): + return "super" + + class C: + @property + @abstractmethod + def test(self): + "abstract method" + + class B(A, C): + + @property + def test(self): + super() #@ + + """ + node = extract_node(code) + inferred = next(node.infer()) + test = inferred.getattr("test") + assert len(test) == 2 + + +def test_infer_generated_setter(): + code = """ + class A: + @property + def test(self): + pass + A.test.setter + """ + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, nodes.FunctionDef) + assert isinstance(inferred.args, nodes.Arguments) + # This line used to crash because property generated functions + # did not have args properly set + assert list(inferred.nodes_of_class(nodes.Const)) == [] + + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_lookup.py b/tests/unittest_lookup.py index 58effd53..84fd5433 100644 --- a/tests/unittest_lookup.py +++ b/tests/unittest_lookup.py @@ -1,8 +1,10 @@ # Copyright (c) 2007-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2010 Daniel Harding <dharding@gmail.com> -# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER diff --git a/tests/unittest_manager.py b/tests/unittest_manager.py index b56a0580..d7878b59 100644 --- a/tests/unittest_manager.py +++ b/tests/unittest_manager.py @@ -1,12 +1,17 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2006, 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2013 AndroWiiid <androwiiid@gmail.com> -# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2017 Chris Philip <chrisp533@gmail.com> # Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com> # Copyright (c) 2017 ioanatia <ioanatia@users.noreply.github.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com> +# Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.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/master/COPYING.LESSER @@ -67,6 +72,17 @@ class AstroidManagerTest( exceptions.AstroidBuildingError, self.manager.ast_from_file, "unhandledName" ) + def test_ast_from_string(self): + filepath = unittest.__file__ + dirname = os.path.dirname(filepath) + modname = os.path.basename(dirname) + with open(filepath, "r") as file: + data = file.read() + ast = self.manager.ast_from_string(data, modname, filepath) + self.assertEqual(ast.name, "unittest") + self.assertEqual(ast.file, filepath) + self.assertIn("unittest", self.manager.astroid_cache) + def test_do_not_expose_main(self): obj = self.manager.ast_from_module_name("__main__") self.assertEqual(obj.name, "__main__") diff --git a/tests/unittest_modutils.py b/tests/unittest_modutils.py index 1747dc22..b5c41bf0 100644 --- a/tests/unittest_modutils.py +++ b/tests/unittest_modutils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2014-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> @@ -7,6 +7,9 @@ # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2018 Mario Corchero <mcorcherojim@bloomberg.net> # Copyright (c) 2018 Mario Corchero <mariocj89@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com> +# Copyright (c) 2019 markmcclain <markmcclain@users.noreply.github.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/master/COPYING.LESSER @@ -127,14 +130,6 @@ class ModPathFromFileTest(unittest.TestCase): ["xml", "etree", "ElementTree"], ) - def test_knownValues_modpath_from_file_2(self): - self.assertEqual( - modutils.modpath_from_file( - "unittest_modutils.py", {os.getcwd(): "arbitrary.pkg"} - ), - ["arbitrary", "pkg", "unittest_modutils"], - ) - def test_raise_modpath_from_file_Exception(self): self.assertRaises(Exception, modutils.modpath_from_file, "/turlututu") diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 5fbef628..5b6a39e3 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -1,6 +1,6 @@ # Copyright (c) 2006-2007, 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com> -# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> @@ -8,9 +8,13 @@ # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2017 rr- <rr-@sakuya.pl> # Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com> +# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com> # Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> +# Copyright (c) 2019-2020 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Alex Hall <alex.mojaki@gmail.com> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.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/master/COPYING.LESSER @@ -22,6 +26,7 @@ import sys import textwrap import unittest import copy +import platform import pytest import six @@ -147,21 +152,20 @@ def function(var): ast = abuilder.string_build(code) self.assertEqual(ast.as_string(), code) - @test_utils.require_version("3.0") - @unittest.expectedFailure def test_3k_annotations_and_metaclass(self): - code_annotations = textwrap.dedent( - ''' - def function(var:int): + code = ''' + def function(var: int): nonlocal counter class Language(metaclass=Natural): """natural language""" ''' - ) + code_annotations = textwrap.dedent(code) + # pylint: disable=line-too-long + expected = 'def function(var: int):\n nonlocal counter\n\n\nclass Language(metaclass=Natural):\n """natural language"""' ast = abuilder.string_build(code_annotations) - self.assertEqual(ast.as_string(), code_annotations) + self.assertEqual(ast.as_string().strip(), expected) def test_ellipsis(self): ast = abuilder.string_build("a[...]").body[0] @@ -254,7 +258,12 @@ class D(metaclass=abc.ABCMeta): ast = abuilder.string_build(code) self.assertEqual(ast.as_string().strip(), code.strip()) - @pytest.mark.skipif(sys.version_info[:2] < (3, 6), reason="needs f-string support") + # This test is disabled on PyPy because we cannot get a proper release on TravisCI that has + # proper support for f-strings (we need 7.2 at least) + @pytest.mark.skipif( + sys.version_info[:2] < (3, 6) or platform.python_implementation() == "PyPy", + reason="Needs f-string support.", + ) def test_f_strings(self): code = r''' a = f"{'a'}" @@ -1131,6 +1140,47 @@ def test_type_comments_arguments(): assert actual_arg.as_string() == expected_arg +@pytest.mark.skipif( + not PY38, reason="needs to be able to parse positional only arguments" +) +def test_type_comments_posonly_arguments(): + module = builder.parse( + """ + def f_arg_comment( + a, # type: int + b, # type: int + /, + c, # type: Optional[int] + d, # type: Optional[int] + *, + e, # type: float + f, # type: float + ): + # type: (...) -> None + pass + """ + ) + expected_annotations = [ + [["int", "int"], ["Optional[int]", "Optional[int]"], ["float", "float"]] + ] + for node, expected_types in zip(module.body, expected_annotations): + assert len(node.type_comment_args) == 1 + if PY38: + assert isinstance(node.type_comment_args[0], astroid.Const) + assert node.type_comment_args[0].value == Ellipsis + else: + assert isinstance(node.type_comment_args[0], astroid.Ellipsis) + type_comments = [ + node.args.type_comment_posonlyargs, + node.args.type_comment_args, + node.args.type_comment_kwonlyargs, + ] + for expected_args, actual_args in zip(expected_types, type_comments): + assert len(expected_args) == len(actual_args) + for expected_arg, actual_arg in zip(expected_args, actual_args): + assert actual_arg.as_string() == expected_arg + + def test_is_generator_for_yield_assignments(): node = astroid.extract_node( """ @@ -1285,5 +1335,30 @@ def test_const_itered(): assert [elem.value for elem in itered] == list("string") +def test_is_generator_for_yield_in_while(): + code = """ + def paused_iter(iterable): + while True: + # Continue to yield the same item until `next(i)` or `i.send(False)` + while (yield value): + pass + """ + node = astroid.extract_node(code) + assert bool(node.is_generator()) + + +def test_is_generator_for_yield_in_if(): + code = """ + import asyncio + + def paused_iter(iterable): + if (yield from asyncio.sleep(0.01)): + pass + return + """ + node = astroid.extract_node(code) + assert bool(node.is_generator()) + + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_object_model.py b/tests/unittest_object_model.py index ac42485a..5301a992 100644 --- a/tests/unittest_object_model.py +++ b/tests/unittest_object_model.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2016-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -58,7 +59,7 @@ class InstanceModelTest(unittest.TestCase): self.assertIsInstance(attr, astroid.Const) self.assertEqual(attr.value, 42) - @unittest.expectedFailure + @pytest.mark.xfail(reason="Instance lookup cannot override object model") def test_instance_local_attributes_overrides_object_model(self): # The instance lookup needs to be changed in order for this to work. ast_node = builder.extract_node( @@ -144,30 +145,6 @@ class ClassModelTest(unittest.TestCase): self.assertIsInstance(inferred, astroid.Const) self.assertEqual(inferred.value, "first") - @test_utils.require_version(maxver="3.0") - def test__mro__old_style(self): - ast_node = builder.extract_node( - """ - class A: - pass - A.__mro__ - """ - ) - with self.assertRaises(exceptions.InferenceError): - next(ast_node.infer()) - - @test_utils.require_version(maxver="3.0") - def test__subclasses__old_style(self): - ast_node = builder.extract_node( - """ - class A: - pass - A.__subclasses__ - """ - ) - with self.assertRaises(exceptions.InferenceError): - next(ast_node.infer()) - def test_class_model_correct_mro_subclasses_proxied(self): ast_nodes = builder.extract_node( """ @@ -354,7 +331,7 @@ class FunctionModelTest(unittest.TestCase): inferred = next(node.infer()) assert inferred is util.Uninferable - @unittest.expectedFailure + @pytest.mark.xfail(reason="Descriptors cannot infer what self is") def test_descriptor_not_inferrring_self(self): # We can't infer __get__(X, Y)() when the bounded function # uses self, because of the tree's parent not being propagating good enough. @@ -506,37 +483,6 @@ class FunctionModelTest(unittest.TestCase): self.assertIsInstance(kwdefaults, astroid.Dict) # self.assertEqual(kwdefaults.getitem('f').value, 'lala') - @test_utils.require_version(maxver="3.0") - def test_function_model_for_python2(self): - ast_nodes = builder.extract_node( - """ - def test(a=1): - "a" - - test.func_name #@ - test.func_doc #@ - test.func_dict #@ - test.func_globals #@ - test.func_defaults #@ - test.func_code #@ - test.func_closure #@ - """ - ) - name = next(ast_nodes[0].infer()) - self.assertIsInstance(name, astroid.Const) - self.assertEqual(name.value, "test") - doc = next(ast_nodes[1].infer()) - self.assertIsInstance(doc, astroid.Const) - self.assertEqual(doc.value, "a") - pydict = next(ast_nodes[2].infer()) - self.assertIsInstance(pydict, astroid.Dict) - pyglobals = next(ast_nodes[3].infer()) - self.assertIsInstance(pyglobals, astroid.Dict) - defaults = next(ast_nodes[4].infer()) - self.assertIsInstance(defaults, astroid.Tuple) - for node in ast_nodes[5:]: - self.assertIs(next(node.infer()), astroid.Uninferable) - @test_utils.require_version(minver="3.8") def test_annotation_positional_only(self): ast_node = builder.extract_node( @@ -640,6 +586,17 @@ class ExceptionModelTest(unittest.TestCase): assert isinstance(inferred, astroid.Const) assert inferred.value == value + def test_unicodedecodeerror(self): + code = """ + try: + raise UnicodeDecodeError("utf-8", "blob", 0, 1, "reason") + except UnicodeDecodeError as error: + error.object[:1] #@ + """ + node = builder.extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, astroid.Const) + def test_import_error(self): ast_nodes = builder.extract_node( """ diff --git a/tests/unittest_objects.py b/tests/unittest_objects.py index 8c56c9fd..a9de6eb2 100644 --- a/tests/unittest_objects.py +++ b/tests/unittest_objects.py @@ -1,6 +1,7 @@ -# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -96,28 +97,6 @@ class SuperTests(unittest.TestCase): self.assertIsInstance(second, bases.Instance) self.assertEqual(second.qname(), "%s.super" % bases.BUILTINS) - @test_utils.require_version(maxver="3.0") - def test_super_on_old_style_class(self): - # super doesn't work on old style class, but leave - # that as an error for pylint. We'll infer Super objects, - # but every call will result in a failure at some point. - node = builder.extract_node( - """ - class OldStyle: - def __init__(self): - super(OldStyle, self) #@ - """ - ) - old = next(node.infer()) - self.assertIsInstance(old, objects.Super) - self.assertIsInstance(old.mro_pointer, nodes.ClassDef) - self.assertEqual(old.mro_pointer.name, "OldStyle") - with self.assertRaises(exceptions.SuperError) as cm: - old.super_mro() - self.assertEqual( - str(cm.exception), "Unable to call super on old-style classes." - ) - @test_utils.require_version(minver="3.0") def test_no_arguments_super(self): ast_nodes = builder.extract_node( diff --git a/tests/unittest_protocols.py b/tests/unittest_protocols.py index 1b70a423..babff51e 100644 --- a/tests/unittest_protocols.py +++ b/tests/unittest_protocols.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER diff --git a/tests/unittest_python3.py b/tests/unittest_python3.py index bc450227..b1759f03 100644 --- a/tests/unittest_python3.py +++ b/tests/unittest_python3.py @@ -5,8 +5,9 @@ # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com> +# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com> # Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com> -# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER diff --git a/tests/unittest_raw_building.py b/tests/unittest_raw_building.py index db32e7da..f782cdb2 100644 --- a/tests/unittest_raw_building.py +++ b/tests/unittest_raw_building.py @@ -1,9 +1,10 @@ # Copyright (c) 2013 AndroWiiid <androwiiid@gmail.com> -# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER diff --git a/tests/unittest_regrtest.py b/tests/unittest_regrtest.py index 91fa8f16..79a86cb2 100644 --- a/tests/unittest_regrtest.py +++ b/tests/unittest_regrtest.py @@ -1,12 +1,13 @@ # Copyright (c) 2006-2008, 2010-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2007 Marien Zwart <marienz@gentoo.org> # Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -306,25 +307,41 @@ def test(): ) next(node.infer()) - @require_version(maxver="3.0") - def test_reassignment_in_except_handler(self): - node = extract_node( - """ - import exceptions - try: - {}["a"] - except KeyError, exceptions.IndexError: - pass - - IndexError #@ + def test_regression_inference_of_self_in_lambda(self): + code = """ + class A: + @b(lambda self: __(self)) + def d(self): + pass """ - ) - self.assertEqual(len(node.inferred()), 1) + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, Instance) + assert inferred.qname() == ".A" class Whatever: a = property(lambda x: x, lambda x: x) +def test_ancestor_looking_up_redefined_function(): + code = """ + class Foo: + def _format(self): + pass + + def format(self): + self.format = self._format + self.format() + Foo + """ + node = extract_node(code) + inferred = next(node.infer()) + ancestor = next(inferred.ancestors()) + _, found = ancestor.lookup("format") + assert len(found) == 1 + assert isinstance(found[0], nodes.FunctionDef) + + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py index d70b7351..c4597fa6 100644 --- a/tests/unittest_scoped_nodes.py +++ b/tests/unittest_scoped_nodes.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2011, 2013-2015 Google, Inc. -# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2013 Phil Schaf <flying-sheep@web.de> # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> @@ -9,11 +9,16 @@ # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org> # Copyright (c) 2015 Philip Lorenz <philip@bithub.de> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> +# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@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 Derek Gustafson <degustaf@gmail.com> +# Copyright (c) 2018-2019 Ville Skyttä <ville.skytta@iki.fi> # Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com> # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com> +# Copyright (c) 2019 Peter de Blanc <peter@standard.ai> +# Copyright (c) 2019 hippo91 <guillaume.peillex@gmail.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/master/COPYING.LESSER @@ -1005,19 +1010,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): self.assertEqual(astroid["g2"].fromlineno, 9) self.assertEqual(astroid["g2"].tolineno, 10) - @test_utils.require_version(maxver="3.0") - def test_simple_metaclass(self): - astroid = builder.parse( - """ - class Test(object): - __metaclass__ = type - """ - ) - klass = astroid["Test"] - metaclass = klass.metaclass() - self.assertIsInstance(metaclass, scoped_nodes.ClassDef) - self.assertEqual(metaclass.name, "type") - def test_metaclass_error(self): astroid = builder.parse( """ @@ -1028,21 +1020,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): klass = astroid["Test"] self.assertFalse(klass.metaclass()) - @test_utils.require_version(maxver="3.0") - def test_metaclass_imported(self): - astroid = builder.parse( - """ - from abc import ABCMeta - class Test(object): - __metaclass__ = ABCMeta - """ - ) - klass = astroid["Test"] - - metaclass = klass.metaclass() - self.assertIsInstance(metaclass, scoped_nodes.ClassDef) - self.assertEqual(metaclass.name, "ABCMeta") - def test_metaclass_yes_leak(self): astroid = builder.parse( """ @@ -1056,100 +1033,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): klass = astroid["Meta"] self.assertIsNone(klass.metaclass()) - @test_utils.require_version(maxver="3.0") - def test_newstyle_and_metaclass_good(self): - astroid = builder.parse( - """ - from abc import ABCMeta - class Test: - __metaclass__ = ABCMeta - """ - ) - klass = astroid["Test"] - self.assertTrue(klass.newstyle) - self.assertEqual(klass.metaclass().name, "ABCMeta") - astroid = builder.parse( - """ - from abc import ABCMeta - __metaclass__ = ABCMeta - class Test: - pass - """ - ) - klass = astroid["Test"] - self.assertTrue(klass.newstyle) - self.assertEqual(klass.metaclass().name, "ABCMeta") - - @test_utils.require_version(maxver="3.0") - def test_nested_metaclass(self): - astroid = builder.parse( - """ - from abc import ABCMeta - class A(object): - __metaclass__ = ABCMeta - class B: pass - - __metaclass__ = ABCMeta - class C: - __metaclass__ = type - class D: pass - """ - ) - a = astroid["A"] - b = a.locals["B"][0] - c = astroid["C"] - d = c.locals["D"][0] - self.assertEqual(a.metaclass().name, "ABCMeta") - self.assertFalse(b.newstyle) - self.assertIsNone(b.metaclass()) - self.assertEqual(c.metaclass().name, "type") - self.assertEqual(d.metaclass().name, "ABCMeta") - - @test_utils.require_version(maxver="3.0") - def test_parent_metaclass(self): - astroid = builder.parse( - """ - from abc import ABCMeta - class Test: - __metaclass__ = ABCMeta - class SubTest(Test): pass - """ - ) - klass = astroid["SubTest"] - self.assertTrue(klass.newstyle) - metaclass = klass.metaclass() - self.assertIsInstance(metaclass, scoped_nodes.ClassDef) - self.assertEqual(metaclass.name, "ABCMeta") - - @test_utils.require_version(maxver="3.0") - def test_metaclass_ancestors(self): - astroid = builder.parse( - """ - from abc import ABCMeta - - class FirstMeta(object): - __metaclass__ = ABCMeta - - class SecondMeta(object): - __metaclass__ = type - - class Simple(object): - pass - - class FirstImpl(FirstMeta): pass - class SecondImpl(FirstImpl): pass - class ThirdImpl(Simple, SecondMeta): - pass - """ - ) - classes = {"ABCMeta": ("FirstImpl", "SecondImpl"), "type": ("ThirdImpl",)} - for metaclass, names in classes.items(): - for name in names: - impl = astroid[name] - meta = impl.metaclass() - self.assertIsInstance(meta, nodes.ClassDef) - self.assertEqual(meta.name, metaclass) - def test_metaclass_type(self): klass = builder.extract_node( """ @@ -1314,33 +1197,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): else: self.assertEqual(list(expected_value), [node.value for node in slots]) - @test_utils.require_version(maxver="3.0") - def test_slots_py2(self): - module = builder.parse( - """ - class UnicodeSlots(object): - __slots__ = (u"a", u"b", "c") - """ - ) - slots = module["UnicodeSlots"].slots() - self.assertEqual(len(slots), 3) - self.assertEqual(slots[0].value, "a") - self.assertEqual(slots[1].value, "b") - self.assertEqual(slots[2].value, "c") - - @test_utils.require_version(maxver="3.0") - def test_slots_py2_not_implemented(self): - module = builder.parse( - """ - class OldStyle: - __slots__ = ("a", "b") - """ - ) - msg = "The concept of slots is undefined for old-style classes." - with self.assertRaises(NotImplementedError) as cm: - module["OldStyle"].slots() - self.assertEqual(str(cm.exception), msg) - def test_slots_for_dict_keys(self): module = builder.parse( """ @@ -1397,73 +1253,23 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): cls = module["B"] self.assertIsNone(cls.slots()) - def assertEqualMro(self, klass, expected_mro): - self.assertEqual([member.name for member in klass.mro()], expected_mro) + def test_slots_added_dynamically_still_inferred(self): + code = """ + class NodeBase(object): + __slots__ = "a", "b" - @test_utils.require_version(maxver="3.0") - def test_no_mro_for_old_style(self): - node = builder.extract_node( - """ - class Old: pass""" - ) - with self.assertRaises(NotImplementedError) as cm: - node.mro() - self.assertEqual( - str(cm.exception), "Could not obtain mro for " "old-style classes." - ) + if Options.isFullCompat(): + __slots__ += ("c",) - @test_utils.require_version(maxver="3.0") - def test_mro_for_classes_with_old_style_in_mro(self): - node = builder.extract_node( - """ - class Factory: - pass - class ClientFactory(Factory): - pass - class ReconnectingClientFactory(ClientFactory): - pass - class WebSocketAdapterFactory(object): - pass - class WebSocketClientFactory(WebSocketAdapterFactory, ClientFactory): - pass - class WampWebSocketClientFactory(WebSocketClientFactory): - pass - class RetryFactory(WampWebSocketClientFactory, ReconnectingClientFactory): - pas """ - ) - self.assertEqualMro( - node, - [ - "RetryFactory", - "WampWebSocketClientFactory", - "WebSocketClientFactory", - "WebSocketAdapterFactory", - "object", - "ReconnectingClientFactory", - "ClientFactory", - "Factory", - ], - ) + node = builder.extract_node(code) + inferred = next(node.infer()) + slots = inferred.slots() + assert len(slots) == 3, slots + assert [slot.value for slot in slots] == ["a", "b", "c"] - @test_utils.require_version(maxver="3.0") - def test_combined_newstyle_oldstyle_in_mro(self): - node = builder.extract_node( - """ - class Old: - pass - class New(object): - pass - class New1(object): - pass - class New2(New, New1): - pass - class NewOld(New2, Old): #@ - pass - """ - ) - self.assertEqualMro(node, ["NewOld", "New2", "New", "New1", "object", "Old"]) - self.assertTrue(node.newstyle) + def assertEqualMro(self, klass, expected_mro): + self.assertEqual([member.name for member in klass.mro()], expected_mro) def test_with_metaclass_mro(self): astroid = builder.parse( @@ -1741,15 +1547,6 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): static = next(acls.igetattr("static")) self.assertIsInstance(static, scoped_nodes.FunctionDef) - @test_utils.require_version(maxver="3.0") - def test_implicit_metaclass_is_none(self): - cls = builder.extract_node( - """ - class A: pass - """ - ) - self.assertIsNone(cls.implicit_metaclass()) - def test_local_attr_invalid_mro(self): cls = builder.extract_node( """ @@ -2021,6 +1818,12 @@ def test_metaclass_cannot_infer_call_yields_an_instance(): pass """ ), + textwrap.dedent( + """ + def __init__(self: int, other: float, /, **kw): + pass + """ + ), ], ) @test_utils.require_version("3.8") diff --git a/tests/unittest_transforms.py b/tests/unittest_transforms.py index 5df7ffad..922f10f9 100644 --- a/tests/unittest_transforms.py +++ b/tests/unittest_transforms.py @@ -1,7 +1,8 @@ -# Copyright (c) 2015-2017 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net> # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER diff --git a/tests/unittest_utils.py b/tests/unittest_utils.py index 3ae3d6b7..428026b1 100644 --- a/tests/unittest_utils.py +++ b/tests/unittest_utils.py @@ -1,8 +1,9 @@ # Copyright (c) 2008-2010, 2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> # Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com> # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com> # Copyright (c) 2016 Dave Baum <dbaum@google.com> +# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk> # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER @@ -25,7 +25,7 @@ deps = python-dateutil pypy: singledispatch six~=1.12 - wrapt==1.11.* + wrapt~=1.11 coverage<5 setenv = |