diff --git a/.gitignore b/.gitignore index 66d64d3e5d..4b0ef30852 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ astroid.egg-info/ .cache/ .eggs/ .pytest_cache/ +.mypy_cache/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 638720d39d..d30c9386cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ jobs: env: TOXENV=py37 - python: 3.8 env: TOXENV=py38 + - python: 3.9 + env: TOXENV=py39 before_install: - python --version - uname -a diff --git a/ChangeLog b/ChangeLog index cb92a70d11..02852c6978 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,19 +2,80 @@ astroid's ChangeLog =================== +* 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 +* Adds a brain for type object so that it is possible to write `type[int]` in annotation. + + Fixes PyCQA/pylint#4001 + +* Add ``__class_getitem__`` method to ``subprocess.Popen`` brain under Python 3.9 so that it is seen as subscriptable by pylint. + + Fixes PyCQA/pylint#4034 + +* Adds `degrees`, `radians`, which are `numpy ufunc` functions, in the `numpy` brain. Adds `random` function in the `numpy.random` brain. + + Fixes PyCQA/pylint#3856 + +* Fix deprecated importlib methods + + Closes #703 + +* Fix a crash in inference caused by `Uninferable` container elements + + Close #866 + +* Add `python 3.9` support. + +* The flat attribute of ``numpy.ndarray`` is now inferred as an ``numpy.ndarray`` itself. + It should be a ``numpy.flatiter`` instance, but this class is not yet available in the numpy brain. + + Fixes PyCQA/pylint#3640 + +* Fix a bug for dunder methods inference of function objects + + Fixes #819 + +* Fixes a bug in the signature of the ``ndarray.__or__`` method, + in the ``brain_numpy_ndarray.py`` module. + + Fixes #815 + +* Fixes a to-list cast bug in ``starred_assigned_stmts`` method, + in the ``protocols.py` module. + +* Added a brain for ``hypothesis.strategies.composite`` + +* The transpose of a ``numpy.ndarray`` is also a ``numpy.ndarray`` + + Fixes PyCQA/pylint#3387 * Added a brain for ``sqlalchemy.orm.session`` +* Separate string and bytes classes patching + + Fixes PyCQA/pylint#3599 + +* Prevent recursion error for self referential length calls + + Close #777 + * Added missing methods to the brain for ``mechanize``, to fix pylint false positives Close #793 * Added more supported parameters to ``subprocess.check_output`` +* Fix recursion errors with pandas + + Fixes PyCQA/pylint#2843 + Fixes PyCQA/pylint#2811 + * Added exception inference for `UnicodeDecodeError` Close PyCQA/pylint#3639 @@ -23,15 +84,37 @@ Release Date: TBA Close PyCQA/pylint#3583 +* Fixed exception-chaining error messages. -What's New in astroid 2.4.3? -============================ -Release Date: TBA +* Fix failure to infer base class type with multiple inheritance and qualified names + + Fixes #843 + +* Reduce memory usage of astroid's module cache. + +* Remove dependency on `imp`. + + Close #594 + Close #681 + +* Do not crash when encountering starred assignments in enums. + + Close #835 + +* Fix a crash in functools.partial inference when the arguments cannot be determined + + Close PyCQA/pylint#3776 * Fix a crash caused by a lookup of a monkey-patched method Close PyCQA/pylint#3686 +* ``is_generator`` correctly considers `Yield` nodes in `AugAssign` nodes + + This fixes a false positive with the `assignment-from-no-return` pylint check. + + Close PyCQA/pylint#3904 + What's New in astroid 2.4.2? ============================ diff --git a/appveyor.yml b/appveyor.yml index b99fff1828..dd6e193a1f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,6 +13,9 @@ environment: - PYTHON: "C:\\Python37" TOXENV: "py37" + - PYTHON: "C:\\Python38" + TOXENV: "py38" + init: - ps: echo $env:TOXENV - ps: ls C:\Python* diff --git a/astroid/__init__.py b/astroid/__init__.py index e869274e40..de4b39a893 100644 --- a/astroid/__init__.py +++ b/astroid/__init__.py @@ -1,7 +1,7 @@ # Copyright (c) 2006-2013, 2015 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Eevee (Alex Munroe) -# Copyright (c) 2015-2016, 2018 Claudiu Popa +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2016 Derek Gustafson # Copyright (c) 2016 Moises Lopez diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index aa48c537a0..150b11c9a5 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) -# Copyright (c) 2014-2019 Claudiu Popa +# Copyright (c) 2014-2020 Claudiu Popa # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2017 Ceridwen # Copyright (c) 2015 Florian Bruhin @@ -14,6 +14,8 @@ # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2019 Uilian Ries # Copyright (c) 2019 Thomas Hisch +# Copyright (c) 2020 Konrad Weihmann +# Copyright (c) 2020 Felix Mölder # Copyright (c) 2020 Michael # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html @@ -26,9 +28,9 @@ extras_require = {} install_requires = [ - "lazy_object_proxy==1.4.*", + "lazy_object_proxy>=1.4.0", "six~=1.12", - "wrapt~=1.11", + "wrapt>=1.11,<1.13", 'typed-ast>=1.4.0,<1.5;implementation_name== "cpython" and python_version<"3.8"', ] @@ -51,6 +53,7 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] diff --git a/astroid/arguments.py b/astroid/arguments.py index 5f4d90924f..a783311cc7 100644 --- a/astroid/arguments.py +++ b/astroid/arguments.py @@ -3,6 +3,7 @@ # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 Nick Drozd # Copyright (c) 2018 Anthony Sottile +# Copyright (c) 2020 hippo91 # 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 @@ -173,7 +174,7 @@ def infer_argument(self, funcnode, name, context): # Too many arguments given and no variable arguments. if len(self.positional_arguments) > len(funcnode.args.args): - if not funcnode.args.vararg: + if not funcnode.args.vararg and not funcnode.args.posonlyargs: raise exceptions.InferenceError( "Too many positional arguments " "passed to {func!r} that does " @@ -292,7 +293,7 @@ def infer_argument(self, funcnode, name, context): except exceptions.NoDefault: pass raise exceptions.InferenceError( - "No value found for argument {name} to " "{func!r}", + "No value found for argument {arg} to {func!r}", call_site=self, func=funcnode, arg=name, diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index 4b07ac5c0b..b1f811d49e 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -5,10 +5,11 @@ # Copyright (c) 2015 Rene Zhang # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 Ville Skyttä +# Copyright (c) 2019-2020 Bryce Guinta # Copyright (c) 2019 Stanislav Levin # Copyright (c) 2019 David Liu -# Copyright (c) 2019 Bryce Guinta # Copyright (c) 2019 Frédéric Chapoton +# Copyright (c) 2020 Ram Rachum # 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,52 +41,90 @@ OBJECT_DUNDER_NEW = "object.__new__" - -def _extend_str(class_node, rvalue): +STR_CLASS = """ +class whatever(object): + def join(self, iterable): + return {rvalue} + def replace(self, old, new, count=None): + return {rvalue} + def format(self, *args, **kwargs): + return {rvalue} + def encode(self, encoding='ascii', errors=None): + return b'' + def decode(self, encoding='ascii', errors=None): + return u'' + def capitalize(self): + return {rvalue} + def title(self): + return {rvalue} + def lower(self): + return {rvalue} + def upper(self): + return {rvalue} + def swapcase(self): + return {rvalue} + def index(self, sub, start=None, end=None): + return 0 + def find(self, sub, start=None, end=None): + return 0 + def count(self, sub, start=None, end=None): + return 0 + def strip(self, chars=None): + return {rvalue} + def lstrip(self, chars=None): + return {rvalue} + def rstrip(self, chars=None): + return {rvalue} + def rjust(self, width, fillchar=None): + return {rvalue} + def center(self, width, fillchar=None): + return {rvalue} + def ljust(self, width, fillchar=None): + return {rvalue} +""" + + +BYTES_CLASS = """ +class whatever(object): + def join(self, iterable): + return {rvalue} + def replace(self, old, new, count=None): + return {rvalue} + def decode(self, encoding='ascii', errors=None): + return u'' + def capitalize(self): + return {rvalue} + def title(self): + return {rvalue} + def lower(self): + return {rvalue} + def upper(self): + return {rvalue} + def swapcase(self): + return {rvalue} + def index(self, sub, start=None, end=None): + return 0 + def find(self, sub, start=None, end=None): + return 0 + def count(self, sub, start=None, end=None): + return 0 + def strip(self, chars=None): + return {rvalue} + def lstrip(self, chars=None): + return {rvalue} + def rstrip(self, chars=None): + return {rvalue} + def rjust(self, width, fillchar=None): + return {rvalue} + def center(self, width, fillchar=None): + return {rvalue} + def ljust(self, width, fillchar=None): + return {rvalue} +""" + + +def _extend_string_class(class_node, code, rvalue): """function to extend builtin str/unicode class""" - code = dedent( - """ - class whatever(object): - def join(self, iterable): - return {rvalue} - def replace(self, old, new, count=None): - return {rvalue} - def format(self, *args, **kwargs): - return {rvalue} - def encode(self, encoding='ascii', errors=None): - return '' - def decode(self, encoding='ascii', errors=None): - return u'' - def capitalize(self): - return {rvalue} - def title(self): - return {rvalue} - def lower(self): - return {rvalue} - def upper(self): - return {rvalue} - def swapcase(self): - return {rvalue} - def index(self, sub, start=None, end=None): - return 0 - def find(self, sub, start=None, end=None): - return 0 - def count(self, sub, start=None, end=None): - return 0 - def strip(self, chars=None): - return {rvalue} - def lstrip(self, chars=None): - return {rvalue} - def rstrip(self, chars=None): - return {rvalue} - def rjust(self, width, fillchar=None): - return {rvalue} - def center(self, width, fillchar=None): - return {rvalue} - def ljust(self, width, fillchar=None): - return {rvalue} - """ - ) code = code.format(rvalue=rvalue) fake = AstroidBuilder(MANAGER).string_build(code)["whatever"] for method in fake.mymethods(): @@ -106,8 +145,8 @@ def _extend_builtins(class_transforms): _extend_builtins( { - "bytes": partial(_extend_str, rvalue="b''"), - "str": partial(_extend_str, rvalue="''"), + "bytes": partial(_extend_string_class, code=BYTES_CLASS, rvalue="b''"), + "str": partial(_extend_string_class, code=STR_CLASS, rvalue="''"), } ) @@ -165,13 +204,13 @@ def _container_generic_inference(node, context, node_type, transform): if not transformed: try: inferred = next(arg.infer(context=context)) - except (InferenceError, StopIteration): - raise UseInferenceDefault() + except (InferenceError, StopIteration) as exc: + raise UseInferenceDefault from exc if inferred is util.Uninferable: - raise UseInferenceDefault() + raise UseInferenceDefault transformed = transform(inferred) if not transformed or transformed is util.Uninferable: - raise UseInferenceDefault() + raise UseInferenceDefault return transformed @@ -185,6 +224,8 @@ def _container_generic_transform(arg, context, klass, iterables, build_elts): # TODO: Does not handle deduplication for sets. elts = [] for element in arg.elts: + if not element: + continue inferred = helpers.safe_infer(element, context=context) if inferred: evaluated_object = nodes.EvaluatedObject( @@ -267,8 +308,8 @@ def _get_elts(arg, context): is_iterable = lambda n: isinstance(n, (nodes.List, nodes.Tuple, nodes.Set)) try: inferred = next(arg.infer(context)) - except (InferenceError, NameInferenceError): - raise UseInferenceDefault() + except (InferenceError, NameInferenceError) as exc: + raise UseInferenceDefault from exc if isinstance(inferred, nodes.Dict): items = inferred.items elif is_iterable(inferred): @@ -371,12 +412,12 @@ def infer_super(node, context=None): else: try: mro_pointer = next(node.args[0].infer(context=context)) - except InferenceError: - raise UseInferenceDefault + except InferenceError as exc: + raise UseInferenceDefault from exc try: mro_type = next(node.args[1].infer(context=context)) - except InferenceError: - raise UseInferenceDefault + except InferenceError as exc: + raise UseInferenceDefault from exc if mro_pointer is util.Uninferable or mro_type is util.Uninferable: # No way we could understand this. @@ -397,8 +438,8 @@ def _infer_getattr_args(node, context): try: obj = next(node.args[0].infer(context=context)) attr = next(node.args[1].infer(context=context)) - except InferenceError: - raise UseInferenceDefault + except InferenceError as exc: + raise UseInferenceDefault from exc if obj is util.Uninferable or attr is util.Uninferable: # If one of the arguments is something we can't infer, @@ -437,8 +478,8 @@ def infer_getattr(node, context=None): # Try to infer the default and return it instead. try: return next(node.args[2].infer(context=context)) - except InferenceError: - raise UseInferenceDefault + except InferenceError as exc: + raise UseInferenceDefault from exc raise UseInferenceDefault @@ -505,8 +546,8 @@ def infer_property(node, context=None): getter = node.args[0] try: inferred = next(getter.infer(context=context)) - except InferenceError: - raise UseInferenceDefault + except InferenceError as exc: + raise UseInferenceDefault from exc if not isinstance(inferred, (nodes.FunctionDef, nodes.Lambda)): raise UseInferenceDefault @@ -673,12 +714,12 @@ def infer_isinstance(callnode, context=None): class_container = _class_or_tuple_to_container( class_or_tuple_node, context=context ) - except InferenceError: - raise UseInferenceDefault + except InferenceError as exc: + raise UseInferenceDefault from exc try: isinstance_bool = helpers.object_isinstance(obj_node, class_container, context) except AstroidTypeError as exc: - raise UseInferenceDefault("TypeError: " + str(exc)) + raise UseInferenceDefault("TypeError: " + str(exc)) from exc except MroError as exc: raise UseInferenceDefault from exc if isinstance_bool is util.Uninferable: @@ -721,6 +762,7 @@ def infer_len(node, context=None): "({len}) given".format(len=len(call.positional_arguments)) ) [argument_node] = call.positional_arguments + try: return nodes.Const(helpers.object_len(argument_node, context=context)) except (AstroidTypeError, InferenceError) as exc: diff --git a/astroid/brain/brain_collections.py b/astroid/brain/brain_collections.py index 669c6ca41d..6594e0c7ac 100644 --- a/astroid/brain/brain_collections.py +++ b/astroid/brain/brain_collections.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2016, 2018 Claudiu Popa +# Copyright (c) 2016, 2018, 2020 Claudiu Popa # Copyright (c) 2016-2017 Łukasz Rogalski # Copyright (c) 2017 Derek Gustafson # Copyright (c) 2018 Ioana Tagirta diff --git a/astroid/brain/brain_dateutil.py b/astroid/brain/brain_dateutil.py index 9fdb9fde00..47b443f5f1 100644 --- a/astroid/brain/brain_dateutil.py +++ b/astroid/brain/brain_dateutil.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2015 raylu # Copyright (c) 2016 Ceridwen diff --git a/astroid/brain/brain_fstrings.py b/astroid/brain/brain_fstrings.py index 298d58afa9..fe9911a74e 100644 --- a/astroid/brain/brain_fstrings.py +++ b/astroid/brain/brain_fstrings.py @@ -1,8 +1,9 @@ -# Copyright (c) 2017-2018 Claudiu Popa +# Copyright (c) 2017-2018, 2020 Claudiu Popa +# Copyright (c) 2020 Karthikeyan Singaravelan # 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 -import collections +import collections.abc import sys import astroid @@ -19,7 +20,7 @@ def _clone_node_with_lineno(node, parent, lineno): new_node = cls(**init_params) if hasattr(node, "postinit") and _astroid_fields: for param, child in postinit_params.items(): - if child and not isinstance(child, collections.Sequence): + if child and not isinstance(child, collections.abc.Sequence): cloned_child = _clone_node_with_lineno( node=child, lineno=new_node.lineno, parent=new_node ) diff --git a/astroid/brain/brain_functools.py b/astroid/brain/brain_functools.py index d6c60691d7..f943f71ab5 100644 --- a/astroid/brain/brain_functools.py +++ b/astroid/brain/brain_functools.py @@ -86,11 +86,14 @@ def _functools_partial_inference(node, context=None): # Determine if the passed keywords into the callsite are supported # by the wrapped function. - function_parameters = chain( - inferred_wrapped_function.args.args or (), - inferred_wrapped_function.args.posonlyargs or (), - inferred_wrapped_function.args.kwonlyargs or (), - ) + if not inferred_wrapped_function.args: + function_parameters = [] + else: + function_parameters = chain( + inferred_wrapped_function.args.args or (), + inferred_wrapped_function.args.posonlyargs or (), + inferred_wrapped_function.args.kwonlyargs or (), + ) parameter_names = set( param.name for param in function_parameters diff --git a/astroid/brain/brain_gi.py b/astroid/brain/brain_gi.py index e49f3a22ed..2c65d9fbfe 100644 --- a/astroid/brain/brain_gi.py +++ b/astroid/brain/brain_gi.py @@ -2,7 +2,7 @@ # Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Cole Robinson -# Copyright (c) 2015-2016, 2018 Claudiu Popa +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2015 David Shea # Copyright (c) 2016 Jakub Wilk diff --git a/astroid/brain/brain_hashlib.py b/astroid/brain/brain_hashlib.py index eb34e1590b..958936284e 100644 --- a/astroid/brain/brain_hashlib.py +++ b/astroid/brain/brain_hashlib.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016, 2018 Claudiu Popa +# Copyright (c) 2016, 2018, 2020 Claudiu Popa # Copyright (c) 2018 David Poirier # Copyright (c) 2018 wgehalo # Copyright (c) 2018 Ioana Tagirta diff --git a/astroid/brain/brain_http.py b/astroid/brain/brain_http.py index b16464e8e5..f4158756dc 100644 --- a/astroid/brain/brain_http.py +++ b/astroid/brain/brain_http.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2019 Claudiu Popa +# Copyright (c) 2019-2020 Claudiu Popa # 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_hypothesis.py b/astroid/brain/brain_hypothesis.py new file mode 100644 index 0000000000..be79151476 --- /dev/null +++ b/astroid/brain/brain_hypothesis.py @@ -0,0 +1,53 @@ +# 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 hook for the Hypothesis library. + +Without this hook pylint reports no-value-for-parameter for use of strategies +defined using the `@hypothesis.strategies.composite` decorator. For example: + + from hypothesis import strategies as st + + @st.composite + def a_strategy(draw): + return draw(st.integers()) + + a_strategy() + +""" + +import astroid + +COMPOSITE_NAMES = ( + "composite", + "st.composite", + "strategies.composite", + "hypothesis.strategies.composite", +) + + +def is_decorated_with_st_composite(node): + """Return True if a decorated node has @st.composite applied.""" + if node.decorators and node.args.args and node.args.args[0].name == "draw": + for decorator_attribute in node.decorators.nodes: + if decorator_attribute.as_string() in COMPOSITE_NAMES: + return True + return False + + +def remove_draw_parameter_from_composite_strategy(node): + """Given that the FunctionDef is decorated with @st.composite, remove the + first argument (`draw`) - it's always supplied by Hypothesis so we don't + need to emit the no-value-for-parameter lint. + """ + del node.args.args[0] + del node.args.annotations[0] + del node.args.type_comment_args[0] + return node + + +astroid.MANAGER.register_transform( + node_class=astroid.FunctionDef, + transform=remove_draw_parameter_from_composite_strategy, + predicate=is_decorated_with_st_composite, +) diff --git a/astroid/brain/brain_io.py b/astroid/brain/brain_io.py index c74531129d..884544b1fa 100644 --- a/astroid/brain/brain_io.py +++ b/astroid/brain/brain_io.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016, 2018 Claudiu Popa +# Copyright (c) 2016, 2018, 2020 Claudiu Popa # 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 88623a82a5..91a9ce011e 100644 --- a/astroid/brain/brain_mechanize.py +++ b/astroid/brain/brain_mechanize.py @@ -1,7 +1,8 @@ # Copyright (c) 2012-2013 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015-2016, 2018 Claudiu Popa +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2016 Ceridwen +# Copyright (c) 2020 Peter Kolbus # 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_multiprocessing.py b/astroid/brain/brain_multiprocessing.py index 3629b03217..4ffd97806f 100644 --- a/astroid/brain/brain_multiprocessing.py +++ b/astroid/brain/brain_multiprocessing.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016, 2018 Claudiu Popa +# Copyright (c) 2016, 2018, 2020 Claudiu Popa # Copyright (c) 2019 Hugo van Kemenade # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py index 13fcf793f7..0387793888 100644 --- a/astroid/brain/brain_namedtuple_enum.py +++ b/astroid/brain/brain_namedtuple_enum.py @@ -13,6 +13,8 @@ # Copyright (c) 2017 Łukasz Rogalski # Copyright (c) 2018 Ville Skyttä # Copyright (c) 2019 Ashley Whetter +# Copyright (c) 2020 hippo91 +# Copyright (c) 2020 Ram Rachum # 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 @@ -51,8 +53,8 @@ def _infer_first(node, context): raise UseInferenceDefault() else: return value - except StopIteration: - raise InferenceError() + except StopIteration as exc: + raise InferenceError from exc def _find_func_form_arguments(node, context): @@ -88,7 +90,7 @@ def infer_func_form(node, base_type, context=None, enum=False): name, names = _find_func_form_arguments(node, context) try: attributes = names.value.replace(",", " ").split() - except AttributeError: + except AttributeError as exc: if not enum: attributes = [ _infer_first(const, context).value for const in names.elts @@ -117,11 +119,11 @@ def infer_func_form(node, base_type, context=None, enum=False): _infer_first(const, context).value for const in names.elts ] else: - raise AttributeError + raise AttributeError from exc if not attributes: - raise AttributeError - except (AttributeError, exceptions.InferenceError): - raise UseInferenceDefault() + raise AttributeError from exc + except (AttributeError, exceptions.InferenceError) as exc: + raise UseInferenceDefault from exc attributes = [attr for attr in attributes if " " not in attr] @@ -312,7 +314,6 @@ def infer_enum_class(node): if any(not isinstance(value, nodes.AssignName) for value in values): continue - targets = [] stmt = values[0].statement() if isinstance(stmt, nodes.Assign): if isinstance(stmt.targets[0], nodes.Tuple): @@ -336,6 +337,8 @@ def infer_enum_class(node): new_targets = [] for target in targets: + if isinstance(target, nodes.Starred): + continue # Replace all the assignments with our mocked class. classdef = dedent( """ @@ -399,14 +402,29 @@ def infer_typing_namedtuple_class(class_node, context=None): return iter((generated_class_node,)) +def infer_typing_namedtuple_function(node, context=None): + """ + Starting with python3.9, NamedTuple is a function of the typing module. + The class NamedTuple is build dynamically through a call to `type` during + initialization of the `_NamedTuple` variable. + """ + klass = extract_node( + """ + from typing import _NamedTuple + _NamedTuple + """ + ) + return klass.infer(context) + + def infer_typing_namedtuple(node, context=None): """Infer a typing.NamedTuple(...) call.""" # This is essentially a namedtuple with different arguments # so we extract the args and infer a named tuple. try: func = next(node.func.infer()) - except InferenceError: - raise UseInferenceDefault + except InferenceError as exc: + raise UseInferenceDefault from exc if func.qname() != "typing.NamedTuple": raise UseInferenceDefault @@ -450,6 +468,11 @@ def infer_typing_namedtuple(node, context=None): MANAGER.register_transform( nodes.ClassDef, inference_tip(infer_typing_namedtuple_class), _has_namedtuple_base ) +MANAGER.register_transform( + nodes.FunctionDef, + inference_tip(infer_typing_namedtuple_function), + lambda node: node.name == "NamedTuple" and node.parent.name == "typing", +) MANAGER.register_transform( nodes.Call, inference_tip(infer_typing_namedtuple), _looks_like_typing_namedtuple ) diff --git a/astroid/brain/brain_nose.py b/astroid/brain/brain_nose.py index d0280a3fff..5d7d83a67a 100644 --- a/astroid/brain/brain_nose.py +++ b/astroid/brain/brain_nose.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2016 Ceridwen # 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 62dfe991c0..b66fc83ac8 100644 --- a/astroid/brain/brain_numpy_core_fromnumeric.py +++ b/astroid/brain/brain_numpy_core_fromnumeric.py @@ -1,4 +1,5 @@ # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 58aa0a9859..534ae87205 100644 --- a/astroid/brain/brain_numpy_core_function_base.py +++ b/astroid/brain/brain_numpy_core_function_base.py @@ -1,4 +1,5 @@ # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 b2e32bc85b..55be31e035 100644 --- a/astroid/brain/brain_numpy_core_multiarray.py +++ b/astroid/brain/brain_numpy_core_multiarray.py @@ -1,4 +1,5 @@ # Copyright (c) 2019-2020 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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_numeric.py b/astroid/brain/brain_numpy_core_numeric.py index 2a6f37e392..53e3a94e3d 100644 --- a/astroid/brain/brain_numpy_core_numeric.py +++ b/astroid/brain/brain_numpy_core_numeric.py @@ -1,4 +1,5 @@ # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 6ac4a14633..c903cbd6ee 100644 --- a/astroid/brain/brain_numpy_core_numerictypes.py +++ b/astroid/brain/brain_numpy_core_numerictypes.py @@ -1,4 +1,5 @@ # Copyright (c) 2019-2020 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 @@ -20,12 +21,14 @@ def numpy_core_numerictypes_transform(): # different types defined in numerictypes.py class generic(object): def __init__(self, value): - self.T = None + self.T = np.ndarray([0, 0]) self.base = None self.data = None self.dtype = None self.flags = None - self.flat = None + # Should be a numpy.flatiter instance but not available for now + # Putting an array instead so that iteration and indexing are authorized + self.flat = np.ndarray([0, 0]) self.imag = None self.itemsize = None self.nbytes = None diff --git a/astroid/brain/brain_numpy_core_umath.py b/astroid/brain/brain_numpy_core_umath.py index 897961eab9..73613b86b2 100644 --- a/astroid/brain/brain_numpy_core_umath.py +++ b/astroid/brain/brain_numpy_core_umath.py @@ -1,9 +1,11 @@ # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 - +#  Note: starting with version 1.18 numpy module has `__getattr__` method which prevent `pylint` to emit `no-member` message for +#  all numpy's attributes. (see pylint's module typecheck in `_emit_no_member` function) """Astroid hooks for numpy.core.umath module.""" import astroid @@ -76,6 +78,7 @@ def __call__(self, x1, x2, {opt_args:s}): conjugate = FakeUfuncOneArg() cosh = FakeUfuncOneArg() deg2rad = FakeUfuncOneArg() + degrees = FakeUfuncOneArg() exp2 = FakeUfuncOneArg() expm1 = FakeUfuncOneArg() fabs = FakeUfuncOneArg() @@ -90,6 +93,7 @@ def __call__(self, x1, x2, {opt_args:s}): negative = FakeUfuncOneArg() positive = FakeUfuncOneArg() rad2deg = FakeUfuncOneArg() + radians = FakeUfuncOneArg() reciprocal = FakeUfuncOneArg() rint = FakeUfuncOneArg() sign = FakeUfuncOneArg() @@ -102,6 +106,7 @@ def __call__(self, x1, x2, {opt_args:s}): trunc = FakeUfuncOneArg() # Two args functions with optional kwargs + add = FakeUfuncTwoArgs() bitwise_and = FakeUfuncTwoArgs() bitwise_or = FakeUfuncTwoArgs() bitwise_xor = FakeUfuncTwoArgs() @@ -129,6 +134,7 @@ def __call__(self, x1, x2, {opt_args:s}): logical_xor = FakeUfuncTwoArgs() maximum = FakeUfuncTwoArgs() minimum = FakeUfuncTwoArgs() + multiply = FakeUfuncTwoArgs() nextafter = FakeUfuncTwoArgs() not_equal = FakeUfuncTwoArgs() power = FakeUfuncTwoArgs() diff --git a/astroid/brain/brain_numpy_ndarray.py b/astroid/brain/brain_numpy_ndarray.py index d40a7dd0b3..39ad81147b 100644 --- a/astroid/brain/brain_numpy_ndarray.py +++ b/astroid/brain/brain_numpy_ndarray.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa +# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa # Copyright (c) 2016 Ceridwen # Copyright (c) 2017-2020 hippo91 @@ -17,13 +17,15 @@ def infer_numpy_ndarray(node, context=None): class ndarray(object): def __init__(self, shape, dtype=float, buffer=None, offset=0, strides=None, order=None): - self.T = None + self.T = numpy.ndarray([0, 0]) self.base = None self.ctypes = None self.data = None self.dtype = None self.flags = None - self.flat = None + # Should be a numpy.flatiter instance but not available for now + # Putting an array instead so that iteration and indexing are authorized + self.flat = np.ndarray([0, 0]) self.imag = np.ndarray([0, 0]) self.itemsize = None self.nbytes = None @@ -71,7 +73,7 @@ def __mod__(self, value): return numpy.ndarray([0, 0]) def __mul__(self, value): return numpy.ndarray([0, 0]) def __ne__(self, value): return numpy.ndarray([0, 0]) def __neg__(self): return numpy.ndarray([0, 0]) - def __or__(self): return numpy.ndarray([0, 0]) + def __or__(self, value): return numpy.ndarray([0, 0]) def __pos__(self): return numpy.ndarray([0, 0]) def __pow__(self): return numpy.ndarray([0, 0]) def __repr__(self): return str() diff --git a/astroid/brain/brain_numpy_random_mtrand.py b/astroid/brain/brain_numpy_random_mtrand.py index cffdceeff0..70d11ea4fe 100644 --- a/astroid/brain/brain_numpy_random_mtrand.py +++ b/astroid/brain/brain_numpy_random_mtrand.py @@ -1,4 +1,5 @@ # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 @@ -44,6 +45,7 @@ def randint(low, high=None, size=None, dtype='l'): import numpy return numpy.ndarray((1,1)) def randn(*args): return uninferable + def random(size=None): return uninferable def random_integers(low, high=None, size=None): return uninferable def random_sample(size=None): return uninferable def rayleigh(scale=1.0, size=None): return uninferable diff --git a/astroid/brain/brain_numpy_utils.py b/astroid/brain/brain_numpy_utils.py index b29d2719c0..96e98bb11f 100644 --- a/astroid/brain/brain_numpy_utils.py +++ b/astroid/brain/brain_numpy_utils.py @@ -1,5 +1,5 @@ +# Copyright (c) 2019-2020 Claudiu Popa # Copyright (c) 2019-2020 hippo91 -# Copyright (c) 2019 Claudiu Popa # 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_pytest.py b/astroid/brain/brain_pytest.py index 56202ab85f..a1f9412c80 100644 --- a/astroid/brain/brain_pytest.py +++ b/astroid/brain/brain_pytest.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, 2018 Claudiu Popa +# Copyright (c) 2014-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2014 Jeff Quast # Copyright (c) 2014 Google, Inc. # Copyright (c) 2016 Florian Bruhin diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py index b703b373e4..b59f72df36 100644 --- a/astroid/brain/brain_qt.py +++ b/astroid/brain/brain_qt.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2016 Ceridwen # Copyright (c) 2017 Roy Wright # Copyright (c) 2018 Ashley Whetter diff --git a/astroid/brain/brain_random.py b/astroid/brain/brain_random.py index 5ec858a1c4..ee116474b2 100644 --- a/astroid/brain/brain_random.py +++ b/astroid/brain/brain_random.py @@ -47,8 +47,8 @@ def infer_random_sample(node, context=None): try: elts = random.sample(inferred_sequence.elts, length.value) - except ValueError: - raise astroid.UseInferenceDefault + except ValueError as exc: + raise astroid.UseInferenceDefault from exc new_node = astroid.List( lineno=node.lineno, col_offset=node.col_offset, parent=node.scope() diff --git a/astroid/brain/brain_scipy_signal.py b/astroid/brain/brain_scipy_signal.py index 996300d487..31908b8ca6 100755 --- a/astroid/brain/brain_scipy_signal.py +++ b/astroid/brain/brain_scipy_signal.py @@ -1,4 +1,5 @@ # Copyright (c) 2019 Valentin Valls +# Copyright (c) 2020 Claudiu Popa # 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 46d9fa3290..389037f285 100644 --- a/astroid/brain/brain_six.py +++ b/astroid/brain/brain_six.py @@ -1,6 +1,7 @@ # Copyright (c) 2014-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2018 Bryce Guinta +# Copyright (c) 2020 Ram Rachum # 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 @@ -145,8 +146,8 @@ def _six_fail_hook(modname): attribute = modname[start_index:].lstrip(".").replace(".", "_") try: import_attr = module.getattr(attribute)[0] - except AttributeInferenceError: - raise AstroidBuildingError(modname=modname) + except AttributeInferenceError as exc: + raise AstroidBuildingError(modname=modname) from exc if isinstance(import_attr, nodes.Import): submodule = MANAGER.ast_from_module_name(import_attr.names[0][0]) return submodule diff --git a/astroid/brain/brain_ssl.py b/astroid/brain/brain_ssl.py index 2ae21c3530..febf8cb6a1 100644 --- a/astroid/brain/brain_ssl.py +++ b/astroid/brain/brain_ssl.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016, 2018 Claudiu Popa +# Copyright (c) 2016, 2018, 2020 Claudiu Popa # Copyright (c) 2016 Ceridwen # Copyright (c) 2019 Benjamin Elven <25181435+S3ntinelX@users.noreply.github.com> diff --git a/astroid/brain/brain_subprocess.py b/astroid/brain/brain_subprocess.py index 2769a03991..c19b32b155 100644 --- a/astroid/brain/brain_subprocess.py +++ b/astroid/brain/brain_subprocess.py @@ -3,6 +3,7 @@ # Copyright (c) 2018 Peter Talley # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2019 Hugo van Kemenade +# Copyright (c) 2020 Peter Pentchev # 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 @@ -13,6 +14,7 @@ import astroid +PY39 = sys.version_info >= (3, 9) PY37 = sys.version_info >= (3, 7) PY36 = sys.version_info >= (3, 6) @@ -146,6 +148,12 @@ def kill(self): "py3_args": py3_args, } ) + if PY39: + code += """ + @classmethod + def __class_getitem__(cls, item): + pass + """ init_lines = textwrap.dedent(init).splitlines() indented_init = "\n".join(" " * 4 + line for line in init_lines) diff --git a/astroid/brain/brain_threading.py b/astroid/brain/brain_threading.py index ba3085b5e4..2c4c36f7b3 100644 --- a/astroid/brain/brain_threading.py +++ b/astroid/brain/brain_threading.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2016, 2018-2019 Claudiu Popa +# Copyright (c) 2016, 2018-2020 Claudiu Popa # Copyright (c) 2017 Łukasz Rogalski # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html diff --git a/astroid/brain/brain_type.py b/astroid/brain/brain_type.py new file mode 100644 index 0000000000..4e82813f90 --- /dev/null +++ b/astroid/brain/brain_type.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +""" +Astroid hooks for type support. + +Starting from python3.9, type object behaves as it had __class_getitem__ method. +However it was not possible to simply add this method inside type's body, otherwise +all types would also have this method. In this case it would have been possible +to write str[int]. +Guido Van Rossum proposed a hack to handle this in the interpreter: +https://github.com/python/cpython/blob/master/Objects/abstract.c#L186-L189 + +This brain follows the same logic. It is no wise to add permanently the __class_getitem__ method +to the type object. Instead we choose to add it only in the case of a subscript node +which inside name node is type. +Doing this type[int] is allowed whereas str[int] is not. + +Thanks to Lukasz Langa for fruitful discussion. +""" +import sys + +from astroid import MANAGER, extract_node, inference_tip, nodes + + +PY39 = sys.version_info >= (3, 9) + + +def _looks_like_type_subscript(node): + """ + Try to figure out if a Name node is used inside a type related subscript + + :param node: node to check + :type node: astroid.node_classes.NodeNG + :return: true if the node is a Name node inside a type related subscript + :rtype: bool + """ + if isinstance(node, nodes.Name) and isinstance(node.parent, nodes.Subscript): + return node.name == "type" + return False + + +def infer_type_sub(node, context=None): + """ + Infer a type[...] subscript + + :param node: node to infer + :type node: astroid.node_classes.NodeNG + :param context: inference context + :type context: astroid.context.InferenceContext + :return: the inferred node + :rtype: nodes.NodeNG + """ + class_src = """ + class type: + def __class_getitem__(cls, key): + return cls + """ + node = extract_node(class_src) + return node.infer(context=context) + + +if PY39: + MANAGER.register_transform( + nodes.Name, inference_tip(infer_type_sub), _looks_like_type_subscript + ) diff --git a/astroid/brain/brain_uuid.py b/astroid/brain/brain_uuid.py index 5a33fc2514..192accfc64 100644 --- a/astroid/brain/brain_uuid.py +++ b/astroid/brain/brain_uuid.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Claudiu Popa +# Copyright (c) 2017-2018, 2020 Claudiu Popa # 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 142764b140..51ce56fda8 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) # Copyright (c) 2013 Phil Schaf -# Copyright (c) 2014-2019 Claudiu Popa +# Copyright (c) 2014-2020 Claudiu Popa # Copyright (c) 2014-2015 Google, Inc. # Copyright (c) 2014 Alexander Presnyakov # Copyright (c) 2015-2016 Ceridwen diff --git a/astroid/context.py b/astroid/context.py index 40cebf222b..4bda945f0f 100644 --- a/astroid/context.py +++ b/astroid/context.py @@ -1,7 +1,8 @@ -# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa +# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 Nick Drozd +# Copyright (c) 2020 Bryce Guinta # 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,10 @@ class InferenceContext: "extra_context", ) + maximum_path_visit = 3 + def __init__(self, path=None, inferred=None): - self.path = path or set() + self.path = path or dict() """ :type: set(tuple(NodeNG, optional(str))) @@ -86,10 +89,10 @@ def push(self, node): Allows one to see if the given node has already been looked at for this inference context""" name = self.lookupname - if (node, name) in self.path: + if self.path.get((node, name), 0) >= self.maximum_path_visit: return True - self.path.add((node, name)) + self.path[(node, name)] = self.path.setdefault((node, name), 0) + 1 return False def clone(self): @@ -105,20 +108,9 @@ def clone(self): clone.extra_context = self.extra_context return clone - def cache_generator(self, key, generator): - """Cache result of generator into dictionary - - Used to cache inference results""" - results = [] - for result in generator: - results.append(result) - yield result - - self.inferred[key] = tuple(results) - @contextlib.contextmanager def restore_path(self): - path = set(self.path) + path = dict(self.path) yield self.path = path diff --git a/astroid/decorators.py b/astroid/decorators.py index 0f3632c48d..cd22312091 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016, 2018 Claudiu Popa +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2015 Florian Bruhin # Copyright (c) 2016 Derek Gustafson @@ -7,6 +7,7 @@ # Copyright (c) 2018 Ashley Whetter # Copyright (c) 2018 HoverHell # Copyright (c) 2018 Bryce Guinta +# Copyright (c) 2020 Ram Rachum # 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 @@ -134,9 +135,9 @@ def raise_if_nothing_inferred(func, instance, args, kwargs): # generator is empty if error.args: # pylint: disable=not-a-mapping - raise exceptions.InferenceError(**error.args[0]) + raise exceptions.InferenceError(**error.args[0]) from error raise exceptions.InferenceError( "StopIteration raised without any error information." - ) + ) from error yield from generator diff --git a/astroid/exceptions.py b/astroid/exceptions.py index 08e72c1370..6801a16a87 100644 --- a/astroid/exceptions.py +++ b/astroid/exceptions.py @@ -1,6 +1,6 @@ # Copyright (c) 2007, 2009-2010, 2013 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015-2018 Claudiu Popa +# Copyright (c) 2015-2018, 2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2016 Derek Gustafson # Copyright (c) 2018 Bryce Guinta diff --git a/astroid/helpers.py b/astroid/helpers.py index 8ab687999e..7a9fae177f 100644 --- a/astroid/helpers.py +++ b/astroid/helpers.py @@ -1,6 +1,9 @@ # Copyright (c) 2015-2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2018 Bryce Guinta +# Copyright (c) 2020 Simon Hewitt +# Copyright (c) 2020 Bryce Guinta +# Copyright (c) 2020 Ram Rachum # 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 @@ -237,13 +240,32 @@ def object_len(node, context=None): :raises AstroidTypeError: If an invalid node is returned from __len__ method or no __len__ method exists :raises InferenceError: If the given node cannot be inferred - or if multiple nodes are inferred + or if multiple nodes are inferred or if the code executed in python + would result in a infinite recursive check for length :rtype int: Integer length of node """ # pylint: disable=import-outside-toplevel; circular import from astroid.objects import FrozenSet inferred_node = safe_infer(node, context=context) + + # prevent self referential length calls from causing a recursion error + # see https://github.com/PyCQA/astroid/issues/777 + node_frame = node.frame() + if ( + isinstance(node_frame, scoped_nodes.FunctionDef) + and node_frame.name == "__len__" + and inferred_node is not None + and inferred_node._proxied == node_frame.parent + ): + message = ( + "Self referential __len__ function will " + "cause a RecursionError on line {} of {}".format( + node.lineno, node.root().file + ) + ) + raise exceptions.InferenceError(message) + if inferred_node is None or inferred_node is util.Uninferable: raise exceptions.InferenceError(node=node) if isinstance(inferred_node, nodes.Const) and isinstance( diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 3cf5fea579..95b069e0db 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2018 Claudiu Popa +# Copyright (c) 2016-2018, 2020 Claudiu Popa # Copyright (c) 2016 Derek Gustafson # Copyright (c) 2017 Chris Philip # Copyright (c) 2017 Hugo @@ -7,22 +7,19 @@ # Copyright (c) 2018 Nick Drozd # Copyright (c) 2019 Hugo van Kemenade # Copyright (c) 2019 Ashley Whetter +# Copyright (c) 2020 Gergely Kalmar +# Copyright (c) 2020 Peter Kolbus +# Copyright (c) 2020 Raphael Gaschignard import abc import collections import distutils import enum -import imp import os import sys import zipimport -try: - import importlib.machinery - - _HAS_MACHINERY = True -except ImportError: - _HAS_MACHINERY = False +import importlib.machinery try: from functools import lru_cache @@ -37,22 +34,6 @@ "PY_CODERESOURCE PY_COMPILED PY_FROZEN PY_RESOURCE " "PY_SOURCE PY_ZIPMODULE PY_NAMESPACE", ) -_ImpTypes = { - imp.C_BUILTIN: ModuleType.C_BUILTIN, - imp.C_EXTENSION: ModuleType.C_EXTENSION, - imp.PKG_DIRECTORY: ModuleType.PKG_DIRECTORY, - imp.PY_COMPILED: ModuleType.PY_COMPILED, - imp.PY_FROZEN: ModuleType.PY_FROZEN, - imp.PY_SOURCE: ModuleType.PY_SOURCE, -} -if hasattr(imp, "PY_RESOURCE"): - _ImpTypes[imp.PY_RESOURCE] = ModuleType.PY_RESOURCE -if hasattr(imp, "PY_CODERESOURCE"): - _ImpTypes[imp.PY_CODERESOURCE] = ModuleType.PY_CODERESOURCE - - -def _imp_type_to_module_type(imp_type): - return _ImpTypes[imp_type] _ModuleSpec = collections.namedtuple( @@ -114,26 +95,59 @@ def contribute_to_path(self, spec, processed): """Get a list of extra paths where this finder can search.""" -class ImpFinder(Finder): - """A finder based on the imp module.""" +class ImportlibFinder(Finder): + """A finder based on the importlib module.""" + + _SUFFIXES = ( + [(s, ModuleType.C_EXTENSION) for s in importlib.machinery.EXTENSION_SUFFIXES] + + [(s, ModuleType.PY_SOURCE) for s in importlib.machinery.SOURCE_SUFFIXES] + + [(s, ModuleType.PY_COMPILED) for s in importlib.machinery.BYTECODE_SUFFIXES] + ) def find_module(self, modname, module_parts, processed, submodule_path): + if not isinstance(modname, str): + raise TypeError("'modname' must be a str, not {}".format(type(modname))) if submodule_path is not None: submodule_path = list(submodule_path) - try: - stream, mp_filename, mp_desc = imp.find_module(modname, submodule_path) - except ImportError: - return None - - # Close resources. - if stream: - stream.close() - - return ModuleSpec( - name=modname, - location=mp_filename, - module_type=_imp_type_to_module_type(mp_desc[2]), - ) + else: + try: + spec = importlib.util.find_spec(modname) + if spec: + if spec.loader is importlib.machinery.BuiltinImporter: + return ModuleSpec( + name=modname, + location=None, + module_type=ModuleType.C_BUILTIN, + ) + if spec.loader is importlib.machinery.FrozenImporter: + return ModuleSpec( + name=modname, + location=None, + module_type=ModuleType.PY_FROZEN, + ) + except ValueError: + pass + submodule_path = sys.path + + for entry in submodule_path: + package_directory = os.path.join(entry, modname) + for suffix in [".py", importlib.machinery.BYTECODE_SUFFIXES[0]]: + package_file_name = "__init__" + suffix + file_path = os.path.join(package_directory, package_file_name) + if os.path.isfile(file_path): + return ModuleSpec( + name=modname, + location=package_directory, + module_type=ModuleType.PKG_DIRECTORY, + ) + for suffix, type_ in ImportlibFinder._SUFFIXES: + file_name = modname + suffix + file_path = os.path.join(entry, file_name) + if os.path.isfile(file_path): + return ModuleSpec( + name=modname, location=file_path, module_type=type_ + ) + return None def contribute_to_path(self, spec, processed): if spec.location is None: @@ -159,7 +173,7 @@ def contribute_to_path(self, spec, processed): return path -class ExplicitNamespacePackageFinder(ImpFinder): +class ExplicitNamespacePackageFinder(ImportlibFinder): """A finder for the explicit namespace packages, generated through pkg_resources.""" def find_module(self, modname, module_parts, processed, submodule_path): @@ -229,10 +243,12 @@ def contribute_to_path(self, spec, processed): return None -_SPEC_FINDERS = (ImpFinder, ZipFinder) -if _HAS_MACHINERY: - _SPEC_FINDERS += (PathSpecFinder,) -_SPEC_FINDERS += (ExplicitNamespacePackageFinder,) +_SPEC_FINDERS = ( + ImportlibFinder, + ZipFinder, + PathSpecFinder, + ExplicitNamespacePackageFinder, +) def _is_setuptools_namespace(location): @@ -240,7 +256,7 @@ def _is_setuptools_namespace(location): with open(os.path.join(location, "__init__.py"), "rb") as stream: data = stream.read(4096) except IOError: - pass + return None else: extend_path = b"pkgutil" in data and b"extend_path" in data declare_namespace = ( @@ -257,6 +273,16 @@ def _cached_set_diff(left, right): def _precache_zipimporters(path=None): + """ + For each path that has not been already cached + in the sys.path_importer_cache, create a new zipimporter + instance and add it into the cache. + Return a dict associating all paths, stored in the cache, to corresponding + zipimporter instances. + + :param path: paths that has to be added into the cache + :return: association between paths stored in the cache and zipimporter instances + """ pic = sys.path_importer_cache # When measured, despite having the same complexity (O(n)), @@ -272,7 +298,11 @@ def _precache_zipimporters(path=None): pic[entry_path] = zipimport.zipimporter(entry_path) except zipimport.ZipImportError: continue - return pic + return { + key: value + for key, value in pic.items() + if isinstance(value, zipimport.zipimporter) + } def _search_zip(modpath, pic): diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py index 10c659f6de..ae48ac11da 100644 --- a/astroid/interpreter/objectmodel.py +++ b/astroid/interpreter/objectmodel.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2016-2019 Claudiu Popa +# Copyright (c) 2016-2020 Claudiu Popa # Copyright (c) 2016 Derek Gustafson # Copyright (c) 2017-2018 Bryce Guinta # Copyright (c) 2017 Ceridwen @@ -324,6 +324,7 @@ def infer_call_result(self, caller, context=None): doc=func.doc, lineno=func.lineno, col_offset=func.col_offset, + parent=func.parent, ) # pylint: disable=no-member new_func.postinit(func.args, func.body, func.decorators, func.returns) diff --git a/astroid/manager.py b/astroid/manager.py index 82208adf7c..0495032906 100644 --- a/astroid/manager.py +++ b/astroid/manager.py @@ -1,5 +1,5 @@ # Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014-2019 Claudiu Popa +# Copyright (c) 2014-2020 Claudiu Popa # Copyright (c) 2014 BioGeek # Copyright (c) 2014 Google, Inc. # Copyright (c) 2014 Eevee (Alex Munroe) @@ -9,6 +9,7 @@ # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 Nick Drozd # Copyright (c) 2019 Raphael Gaschignard +# Copyright (c) 2020 Raphael Gaschignard # Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.com> # Copyright (c) 2020 Ashley Whetter @@ -236,11 +237,13 @@ def file_from_module_name(self, modname, contextfile): value = exceptions.AstroidImportError( "Failed to import module {modname} with error:\n{error}.", modname=modname, - error=ex, + # we remove the traceback here to save on memory usage (since these exceptions are cached) + error=ex.with_traceback(None), ) self._mod_file_cache[(modname, contextfile)] = value if isinstance(value, exceptions.AstroidBuildingError): - raise value + # we remove the traceback here to save on memory usage (since these exceptions are cached) + raise value.with_traceback(None) return value def ast_from_module(self, module, modname=None): diff --git a/astroid/modutils.py b/astroid/modutils.py index 4e6ed86bf8..4ec8ce954b 100644 --- a/astroid/modutils.py +++ b/astroid/modutils.py @@ -16,6 +16,8 @@ # Copyright (c) 2019 Hugo van Kemenade # Copyright (c) 2019 markmcclain # Copyright (c) 2019 BasPH +# Copyright (c) 2020 hippo91 +# Copyright (c) 2020 Peter Kolbus # 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 @@ -31,7 +33,7 @@ :type BUILTIN_MODULES: dict :var BUILTIN_MODULES: dictionary with builtin module names has key """ -import imp +import importlib.util import os import platform import sys @@ -44,7 +46,6 @@ # 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 @@ -90,11 +91,23 @@ pass if platform.python_implementation() == "PyPy": + # The get_python_lib(standard_lib=True) function does not give valid + # result with pypy in a virtualenv. + # In a virtual environment, with CPython implementation the call to this function returns a path toward + # the binary (its libraries) which has been used to create the virtual environment. + # Not with pypy implementation. + # The only way to retrieve such information is to use the sys.base_prefix hint. + # It's worth noticing that under CPython implementation the return values of + # get_python_lib(standard_lib=True) and get_python_lib(santdard_lib=True, prefix=sys.base_prefix) + # are the same. + # In the lines above, we could have replace the call to get_python_lib(standard=True) + # with the one using prefix=sys.base_prefix but we prefer modifying only what deals with pypy. + STD_LIB_DIRS.add(get_python_lib(standard_lib=True, prefix=sys.base_prefix)) _root = os.path.join(sys.prefix, "lib_pypy") STD_LIB_DIRS.add(_root) try: # real_prefix is defined when running inside virtualenv. - STD_LIB_DIRS.add(os.path.join(sys.real_prefix, "lib_pypy")) + STD_LIB_DIRS.add(os.path.join(sys.base_prefix, "lib_pypy")) except AttributeError: pass del _root @@ -178,110 +191,53 @@ def _cache_normalize_path(path): return result -def load_module_from_name(dotted_name, path=None, use_sys=True): +def load_module_from_name(dotted_name): """Load a Python module from its name. :type dotted_name: str :param dotted_name: python name of a module or package - :type path: list or None - :param 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 - :param use_sys: - 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 """ - return load_module_from_modpath(dotted_name.split("."), path, use_sys) + try: + return sys.modules[dotted_name] + except KeyError: + pass + return importlib.import_module(dotted_name) -def load_module_from_modpath(parts, path: Optional[List[str]] = None, use_sys=1): + +def load_module_from_modpath(parts): """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 '.' - :param 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 - :param use_sys: - 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 """ - if use_sys: - try: - return sys.modules[".".join(parts)] - except KeyError: - pass - modpath = [] - prevmodule = None - for part in parts: - modpath.append(part) - curname = ".".join(modpath) - module = None - if len(modpath) != len(parts): - # even with use_sys=False, should try to get outer packages from sys.modules - module = sys.modules.get(curname) - elif use_sys: - # because it may have been indirectly loaded through a parent - module = sys.modules.get(curname) - if module is None: - mp_file, mp_filename, mp_desc = imp.find_module(part, path) - module = imp.load_module(curname, mp_file, mp_filename, mp_desc) - # mp_file still needs to be closed. - if mp_file: - mp_file.close() - if prevmodule: - setattr(prevmodule, part, module) - _file = getattr(module, "__file__", "") - prevmodule = module - if not _file and util.is_namespace(curname): - continue - if not _file and len(modpath) != len(parts): - raise ImportError("no module in %s" % ".".join(parts[len(modpath) :])) - path = [os.path.dirname(_file)] - return module + return load_module_from_name(".".join(parts)) -def load_module_from_file( - filepath: str, path: Optional[List[str]] = None, use_sys=True -): +def load_module_from_file(filepath: str): """Load a Python module from it's path. :type filepath: str :param filepath: path to the python module or package - :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 - :param use_sys: - 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) - return load_module_from_modpath(modpath, path, use_sys) + return load_module_from_modpath(modpath) def check_modpath_has_init(path, mod_path): @@ -418,7 +374,9 @@ def file_info_from_modpath(modpath, path=None, context_file=None): elif modpath == ["os", "path"]: # FIXME: currently ignoring search_path... return spec.ModuleSpec( - name="os.path", location=os.path.__file__, module_type=imp.PY_SOURCE + name="os.path", + location=os.path.__file__, + module_type=spec.ModuleType.PY_SOURCE, ) return _spec_from_modpath(modpath, path, context) @@ -614,16 +572,22 @@ def is_relative(modname, from_file): from_file = os.path.dirname(from_file) if from_file in sys.path: return False - try: - stream, _, _ = imp.find_module(modname.split(".")[0], [from_file]) - - # Close the stream to avoid ResourceWarnings. - if stream: - stream.close() - return True - except ImportError: + name = os.path.basename(from_file) + file_path = os.path.dirname(from_file) + parent_spec = importlib.util.find_spec(name, from_file) + while parent_spec is None and len(file_path) > 0: + name = os.path.basename(file_path) + "." + name + file_path = os.path.dirname(file_path) + parent_spec = importlib.util.find_spec(name, from_file) + + if parent_spec is None: return False + submodule_spec = importlib.util.find_spec( + name + "." + modname.split(".")[0], parent_spec.submodule_search_locations + ) + return submodule_spec is not None + # internal only functions ##################################################### diff --git a/astroid/node_classes.py b/astroid/node_classes.py index 59b1e31c79..62438e62a6 100644 --- a/astroid/node_classes.py +++ b/astroid/node_classes.py @@ -14,14 +14,16 @@ # Copyright (c) 2017-2020 Ashley Whetter # Copyright (c) 2017, 2019 Łukasz Rogalski # Copyright (c) 2017 rr- +# Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018-2019 hippo91 # Copyright (c) 2018 Nick Drozd # Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 brendanator # Copyright (c) 2018 HoverHell # Copyright (c) 2019 kavins14 # Copyright (c) 2019 kavins14 +# Copyright (c) 2020 Raphael Gaschignard +# Copyright (c) 2020 Bryce Guinta # 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 @@ -354,19 +356,37 @@ def infer(self, context=None, **kwargs): # explicit_inference is not bound, give it self explicitly try: # pylint: disable=not-callable - return self._explicit_inference(self, context, **kwargs) + yield from self._explicit_inference(self, context, **kwargs) + return except exceptions.UseInferenceDefault: pass if not context: - return self._infer(context, **kwargs) + yield from self._infer(context, **kwargs) + return key = (self, context.lookupname, context.callcontext, context.boundnode) if key in context.inferred: - return iter(context.inferred[key]) + yield from context.inferred[key] + return + + generator = self._infer(context, **kwargs) + results = [] + + # Limit inference amount to help with performance issues with + # exponentially exploding possible results. + limit = MANAGER.max_inferable_values + for i, result in enumerate(generator): + if i >= limit: + yield util.Uninferable + break + results.append(result) + yield result - gen = context.cache_generator(key, self._infer(context, **kwargs)) - return util.limit_inference(gen, MANAGER.max_inferable_values) + # Cache generated results for subsequent inferences of the + # same node using the same context + context.inferred[key] = tuple(results) + return def _repr_name(self): """Get a name for nice representation. @@ -950,7 +970,7 @@ def next_sibling(self): try: return stmts[index + 1] except IndexError: - pass + return None def previous_sibling(self): """The previous sibling statement. @@ -2105,6 +2125,11 @@ def get_children(self): yield self.target yield self.value + def _get_yield_nodes_skip_lambdas(self): + """An AugAssign node can contain a Yield node in the value""" + yield from self.value._get_yield_nodes_skip_lambdas() + yield from super()._get_yield_nodes_skip_lambdas() + class Repr(NodeNG): """Class representing an :class:`ast.Repr` node. diff --git a/astroid/protocols.py b/astroid/protocols.py index 2cdf5548be..57e5a2314f 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -14,6 +14,8 @@ # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 HoverHell # Copyright (c) 2019 Hugo van Kemenade +# Copyright (c) 2020 Vilnis Termanis +# Copyright (c) 2020 Ram Rachum # 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 @@ -498,8 +500,8 @@ def _infer_context_manager(self, mgr, context): elif isinstance(inferred, bases.Instance): try: enter = next(inferred.igetattr("__enter__", context=context)) - except (exceptions.InferenceError, exceptions.AttributeInferenceError): - raise exceptions.InferenceError(node=inferred) + except (exceptions.InferenceError, exceptions.AttributeInferenceError) as exc: + raise exceptions.InferenceError(node=inferred) from exc if not isinstance(enter, bases.BoundMethod): raise exceptions.InferenceError(node=enter) yield from enter.infer_call_result(self, context) @@ -686,11 +688,10 @@ def _determine_starred_iteration_lookups(starred, target, lookups): continue # We're done unpacking. - elts = list(elts) packed = nodes.List( ctx=Store, parent=self, lineno=lhs.lineno, col_offset=lhs.col_offset ) - packed.postinit(elts=elts) + packed.postinit(elts=list(elts)) yield packed break diff --git a/astroid/raw_building.py b/astroid/raw_building.py index b261277879..cecae98945 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -11,6 +11,7 @@ # Copyright (c) 2018 Ville Skyttä # Copyright (c) 2018 Nick Drozd # Copyright (c) 2018 Bryce Guinta +# Copyright (c) 2020 Becker Awqatty # Copyright (c) 2020 Robin Jarry # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html @@ -25,6 +26,7 @@ import os import sys import types +import warnings from astroid import bases from astroid import manager @@ -324,8 +326,10 @@ def object_build(self, node, obj): self._done[obj] = node for name in dir(obj): try: - member = getattr(obj, name) - except AttributeError: + with warnings.catch_warnings(): + warnings.filterwarnings("error") + member = getattr(obj, name) + except (AttributeError, DeprecationWarning): # damned ExtensionClass.Base, I know you're there ! attach_dummy_node(node, name) continue diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index 8561e745a0..6e899e17be 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -20,6 +20,9 @@ # Copyright (c) 2018 HoverHell # Copyright (c) 2019 Hugo van Kemenade # Copyright (c) 2019 Peter de Blanc +# Copyright (c) 2020 Peter Kolbus +# Copyright (c) 2020 Tim Martin +# Copyright (c) 2020 Ram Rachum # 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 @@ -1448,6 +1451,7 @@ def extra_decorators(self): decorators.append(assign.value) return decorators + # pylint: disable=invalid-overridden-method @decorators_mod.cachedproperty def type( self @@ -2571,7 +2575,7 @@ def igetattr(self, name, context=None, class_context=True): else: raise exceptions.InferenceError( error.message, target=self, attribute=name, context=context - ) + ) from error def has_dynamic_getattr(self, context=None): """Check if the class has a custom __getattr__ or __getattribute__. @@ -2858,7 +2862,7 @@ def _inferred_bases(self, context=None): for stmt in self.bases: try: - baseobj = next(stmt.infer(context=context)) + baseobj = next(stmt.infer(context=context.clone())) except exceptions.InferenceError: continue if isinstance(baseobj, bases.Instance): diff --git a/astroid/test_utils.py b/astroid/test_utils.py index e22c7a4ffd..5948d4d82d 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) -# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa +# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2016 Jakub Wilk # Copyright (c) 2018 Anthony Sottile diff --git a/astroid/util.py b/astroid/util.py index 3ab7561553..cb3f0ddfea 100644 --- a/astroid/util.py +++ b/astroid/util.py @@ -2,12 +2,12 @@ # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 Nick Drozd +# Copyright (c) 2020 Bryce Guinta # 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 import warnings -from itertools import islice import importlib import lazy_object_proxy @@ -139,26 +139,3 @@ def proxy_alias(alias_name, node_type): }, ) return proxy(lambda: node_type) - - -def limit_inference(iterator, size): - """Limit inference amount. - - Limit inference amount to help with performance issues with - exponentially exploding possible results. - - :param iterator: Inference generator to limit - :type iterator: Iterator(NodeNG) - - :param size: Maximum mount of nodes yielded plus an - Uninferable at the end if limit reached - :type size: int - - :yields: A possibly modified generator - :rtype param: Iterable - """ - yield from islice(iterator, size) - has_more = next(iterator, False) - if has_more is not False: - yield Uninferable - return diff --git a/setup.py b/setup.py index 016fce1816..ffbacf7018 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,12 @@ #!/usr/bin/env python # Copyright (c) 2006, 2009-2010, 2012-2013 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2010-2011 Julien Jehannet -# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa +# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa # Copyright (c) 2014 Google, Inc. # Copyright (c) 2017 Hugo # Copyright (c) 2018-2019 Ashley Whetter # Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Colin Kennedy # 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,10 +15,14 @@ """Setup script for astroid.""" import os import sys +import warnings from setuptools import find_packages, setup from setuptools.command import easy_install # pylint: disable=unused-import from setuptools.command import install_lib # pylint: disable=unused-import +if sys.version_info.major == 3 and sys.version_info.minor <=5: + warnings.warn("You will soon need to upgrade to python 3.6 in order to use the latest version of Astroid.", DeprecationWarning) + real_path = os.path.realpath(__file__) astroid_dir = os.path.dirname(real_path) pkginfo = os.path.join(astroid_dir, "astroid", "__pkginfo__.py") diff --git a/tests/resources.py b/tests/resources.py index 7113f2b15c..20bd41138b 100644 --- a/tests/resources.py +++ b/tests/resources.py @@ -1,8 +1,9 @@ # Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015-2016, 2018-2019 Claudiu Popa +# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa # Copyright (c) 2016 Ceridwen # Copyright (c) 2018 Nick Drozd # Copyright (c) 2019 Ashley Whetter +# Copyright (c) 2020 David Cain # 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/testdata/python3/data/nonregr.py b/tests/testdata/python3/data/nonregr.py index 78765c8544..073135d233 100644 --- a/tests/testdata/python3/data/nonregr.py +++ b/tests/testdata/python3/data/nonregr.py @@ -16,9 +16,7 @@ def toto(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) +import optparse as s_opt class OptionParser(s_opt.OptionParser): diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py index 0a833664b1..e6ff69289a 100644 --- a/tests/unittest_brain.py +++ b/tests/unittest_brain.py @@ -18,11 +18,12 @@ # Copyright (c) 2018 Anthony Sottile # Copyright (c) 2018 Ioana Tagirta # Copyright (c) 2018 Ahmed Azzaoui +# Copyright (c) 2019-2020 Bryce Guinta # Copyright (c) 2019 Ashley Whetter # Copyright (c) 2019 Tomas Novak # Copyright (c) 2019 Hugo van Kemenade # Copyright (c) 2019 Grygorii Iermolenko -# Copyright (c) 2019 Bryce Guinta +# Copyright (c) 2020 Peter Kolbus # 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 @@ -540,8 +541,10 @@ def test_multiprocessing_manager(self): obj = next(module[attr].infer()) self.assertEqual(obj.qname(), "{}.{}".format(bases.BUILTINS, attr)) - array = next(module["array"].infer()) - self.assertEqual(array.qname(), "array.array") + # pypy's implementation of array.__spec__ return None. This causes problems for this inference. + if not hasattr(sys, "pypy_version_info"): + array = next(module["array"].infer()) + self.assertEqual(array.qname(), "array.array") manager = next(module["manager"].infer()) # Verify that we have these attributes @@ -839,6 +842,16 @@ class MyEnum(enum.Enum): assert inferred_tuple_node.as_string() == "(1, 2)" assert inferred_list_node.as_string() == "[2, 4]" + def test_enum_starred_is_skipped(self): + code = """ + from enum import Enum + class ContentType(Enum): + TEXT, PHOTO, VIDEO, GIF, YOUTUBE, *_ = [1, 2, 3, 4, 5, 6] + ContentType.TEXT #@ + """ + node = astroid.extract_node(code) + next(node.infer()) + @unittest.skipUnless(HAS_DATEUTIL, "This test requires the dateutil library.") class DateutilBrainTest(unittest.TestCase): @@ -924,6 +937,42 @@ def test_sys_streams(self): self.assertEqual(raw.name, "FileIO") +@test_utils.require_version("3.9") +class TypeBrain(unittest.TestCase): + def test_type_subscript(self): + """ + Check that type object has the __class_getitem__ method + when it is used as a subscript + """ + src = builder.extract_node( + """ + a: type[int] = int + """ + ) + val_inf = src.annotation.value.inferred()[0] + self.assertIsInstance(val_inf, astroid.ClassDef) + self.assertEqual(val_inf.name, "type") + meth_inf = val_inf.getattr("__class_getitem__")[0] + self.assertIsInstance(meth_inf, astroid.FunctionDef) + + def test_invalid_type_subscript(self): + """ + Check that a type (str for example) that inherits + from type does not have __class_getitem__ method even + when it is used as a subscript + """ + src = builder.extract_node( + """ + a: str[int] = "abc" + """ + ) + val_inf = src.annotation.value.inferred()[0] + self.assertIsInstance(val_inf, astroid.ClassDef) + self.assertEqual(val_inf.name, "str") + with self.assertRaises(astroid.exceptions.AttributeInferenceError): + meth_inf = val_inf.getattr("__class_getitem__")[0] + + @test_utils.require_version("3.6") class TypingBrain(unittest.TestCase): def test_namedtuple_base(self): @@ -1279,6 +1328,13 @@ def test_subprcess_check_output(self): assert isinstance(inferred, astroid.Const) assert isinstance(inferred.value, (str, bytes)) + @test_utils.require_version("3.9") + def test_popen_does_not_have_class_getitem(self): + code = """import subprocess; subprocess.Popen""" + node = astroid.extract_node(code) + inferred = next(node.infer()) + assert "__class_getitem__" in inferred + class TestIsinstanceInference: """Test isinstance builtin inference""" @@ -2020,5 +2076,36 @@ class Other: assert isinstance(name[0], astroid.Unknown) +@pytest.mark.parametrize( + "code,expected_class,expected_value", + [ + ("'hey'.encode()", astroid.Const, b""), + ("b'hey'.decode()", astroid.Const, ""), + ("'hey'.encode().decode()", astroid.Const, ""), + ], +) +def test_str_and_bytes(code, expected_class, expected_value): + node = astroid.extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, expected_class) + assert inferred.value == expected_value + + +def test_no_recursionerror_on_self_referential_length_check(): + """ + Regression test for https://github.com/PyCQA/astroid/issues/777 + """ + with pytest.raises(astroid.InferenceError): + node = astroid.extract_node( + """ + class Crash: + def __len__(self) -> int: + return len(self) + len(Crash()) #@ + """ + ) + node.inferred() + + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_brain_numpy_core_fromnumeric.py b/tests/unittest_brain_numpy_core_fromnumeric.py index fd571f30c2..73ecccc228 100644 --- a/tests/unittest_brain_numpy_core_fromnumeric.py +++ b/tests/unittest_brain_numpy_core_fromnumeric.py @@ -1,6 +1,7 @@ # -*- encoding=utf-8 -*- # Copyright (c) 2019 Ashley Whetter # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 109238a286..df44b64514 100644 --- a/tests/unittest_brain_numpy_core_function_base.py +++ b/tests/unittest_brain_numpy_core_function_base.py @@ -1,6 +1,7 @@ # -*- encoding=utf-8 -*- # Copyright (c) 2019 Ashley Whetter # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 9e945d21f7..cd68db06cf 100644 --- a/tests/unittest_brain_numpy_core_multiarray.py +++ b/tests/unittest_brain_numpy_core_multiarray.py @@ -1,6 +1,7 @@ # -*- encoding=utf-8 -*- # Copyright (c) 2019 hippo91 # Copyright (c) 2019 Ashley Whetter +# Copyright (c) 2020 Claudiu Popa # 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 a39fb19e72..47894ca947 100644 --- a/tests/unittest_brain_numpy_core_numeric.py +++ b/tests/unittest_brain_numpy_core_numeric.py @@ -1,6 +1,7 @@ # -*- encoding=utf-8 -*- # Copyright (c) 2019 Ashley Whetter # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 db4fdd23a0..e05f8eb718 100644 --- a/tests/unittest_brain_numpy_core_numerictypes.py +++ b/tests/unittest_brain_numpy_core_numerictypes.py @@ -1,6 +1,6 @@ # -*- encoding=utf-8 -*- +# Copyright (c) 2017-2018, 2020 Claudiu Popa # Copyright (c) 2017-2020 hippo91 -# Copyright (c) 2017-2018 Claudiu Popa # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2019 Ashley Whetter diff --git a/tests/unittest_brain_numpy_core_umath.py b/tests/unittest_brain_numpy_core_umath.py index 7fe7f34373..acfaeb70f4 100644 --- a/tests/unittest_brain_numpy_core_umath.py +++ b/tests/unittest_brain_numpy_core_umath.py @@ -1,6 +1,7 @@ # -*- encoding=utf-8 -*- # Copyright (c) 2019 hippo91 # Copyright (c) 2019 Ashley Whetter +# Copyright (c) 2020 Claudiu Popa # 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 @@ -36,6 +37,7 @@ class NumpyBrainCoreUmathTest(unittest.TestCase): "conjugate", "cosh", "deg2rad", + "degrees", "exp2", "expm1", "fabs", @@ -50,6 +52,7 @@ class NumpyBrainCoreUmathTest(unittest.TestCase): "negative", "positive", "rad2deg", + "radians", "reciprocal", "rint", "sign", @@ -62,6 +65,7 @@ class NumpyBrainCoreUmathTest(unittest.TestCase): ) two_args_ufunc = ( + "add", "bitwise_and", "bitwise_or", "bitwise_xor", @@ -89,6 +93,7 @@ class NumpyBrainCoreUmathTest(unittest.TestCase): "logical_xor", "maximum", "minimum", + "multiply", "nextafter", "not_equal", "power", @@ -221,11 +226,9 @@ def test_numpy_core_umath_functions_return_type(self): with self.subTest(typ=func_): inferred_values = list(self._inferred_numpy_func_call(func_)) self.assertTrue( - len(inferred_values) == 1 - or len(inferred_values) == 2 - and inferred_values[-1].pytype() is util.Uninferable, + len(inferred_values) == 1, msg="Too much inferred values ({}) for {:s}".format( - inferred_values[-1].pytype(), func_ + inferred_values, func_ ), ) self.assertTrue( diff --git a/tests/unittest_brain_numpy_ndarray.py b/tests/unittest_brain_numpy_ndarray.py index defce47dba..e53ce54066 100644 --- a/tests/unittest_brain_numpy_ndarray.py +++ b/tests/unittest_brain_numpy_ndarray.py @@ -1,6 +1,6 @@ # -*- encoding=utf-8 -*- # Copyright (c) 2017-2020 hippo91 -# Copyright (c) 2017-2018 Claudiu Popa +# Copyright (c) 2017-2018, 2020 Claudiu Popa # Copyright (c) 2018 Bryce Guinta # Copyright (c) 2019 Ashley Whetter @@ -154,7 +154,7 @@ 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"): + for attr_ in ("real", "imag", "shape", "T"): with self.subTest(typ=attr_): inferred_values = list(self._inferred_ndarray_attribute(attr_)) self.assertTrue( diff --git a/tests/unittest_brain_numpy_random_mtrand.py b/tests/unittest_brain_numpy_random_mtrand.py index 20a5d31021..ec2bfcf7c4 100644 --- a/tests/unittest_brain_numpy_random_mtrand.py +++ b/tests/unittest_brain_numpy_random_mtrand.py @@ -1,6 +1,7 @@ # -*- encoding=utf-8 -*- # Copyright (c) 2019 Ashley Whetter # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Claudiu Popa # 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 @@ -55,6 +56,7 @@ class NumpyBrainRandomMtrandTest(unittest.TestCase): "rand": (["args"], []), "randint": (["low", "high", "size", "dtype"], [None, None, "l"]), "randn": (["args"], []), + "random": (["size"], [None]), "random_integers": (["low", "high", "size"], [None, None]), "random_sample": (["size"], [None]), "rayleigh": (["scale", "size"], [1.0, None]), diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py index 22e49e60f1..8f1205d67e 100644 --- a/tests/unittest_builder.py +++ b/tests/unittest_builder.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014-2019 Claudiu Popa +# Copyright (c) 2014-2020 Claudiu Popa # Copyright (c) 2014-2015 Google, Inc. # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2015 Florian Bruhin diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index d99298bcaa..7b80b530e7 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -14,14 +14,18 @@ # Copyright (c) 2017 Calen Pennington # Copyright (c) 2017 David Euresti # Copyright (c) 2017 Derek Gustafson +# Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 Daniel Martin # Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2018 Bryce Guinta # Copyright (c) 2018 Anthony Sottile # Copyright (c) 2019 Stanislav Levin # Copyright (c) 2019 David Liu # Copyright (c) 2019 Ashley Whetter # Copyright (c) 2019 Hugo van Kemenade +# Copyright (c) 2020 Peter Kolbus +# Copyright (c) 2020 hippo91 +# Copyright (c) 2020 Karthikeyan Singaravelan +# Copyright (c) 2020 Bryce Guinta # 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 @@ -428,7 +432,7 @@ def test_del1(self): del undefined_attr """ delete = extract_node(code, __name__) - self.assertRaises(InferenceError, delete.infer) + self.assertRaises(InferenceError, next, delete.infer()) def test_del2(self): code = """ @@ -541,18 +545,6 @@ class Warning(Warning): self.assertEqual(ancestor.root().name, BUILTINS) self.assertRaises(StopIteration, partial(next, ancestors)) - def test_qqch(self): - code = """ - from astroid.modutils import load_module_from_name - xxx = load_module_from_name('__pkginfo__') - """ - ast = parse(code, __name__) - xxx = ast["xxx"] - self.assertSetEqual( - {n.__class__ for n in xxx.inferred()}, - {nodes.Const, util.Uninferable.__class__}, - ) - def test_method_argument(self): code = ''' class ErudiEntitySchema: @@ -1309,7 +1301,7 @@ def get_context_data(self, **kwargs): result = node.inferred() assert len(result) == 2 assert isinstance(result[0], nodes.Dict) - assert result[1] is util.Uninferable + assert isinstance(result[1], nodes.Dict) def test_python25_no_relative_import(self): ast = resources.build_file("data/package/absimport.py") @@ -2083,8 +2075,6 @@ def test_dict_invalid_args(self): def test_str_methods(self): code = """ ' '.decode() #@ - - ' '.encode() #@ ' '.join('abcd') #@ ' '.replace('a', 'b') #@ ' '.format('a') #@ @@ -2106,15 +2096,13 @@ def test_str_methods(self): """ ast = extract_node(code, __name__) self.assertInferConst(ast[0], "") - for i in range(1, 16): + for i in range(1, 15): self.assertInferConst(ast[i], "") - for i in range(16, 19): + for i in range(15, 18): self.assertInferConst(ast[i], 0) def test_unicode_methods(self): code = """ - u' '.encode() #@ - u' '.decode() #@ u' '.join('abcd') #@ u' '.replace('a', 'b') #@ @@ -2137,9 +2125,9 @@ def test_unicode_methods(self): """ ast = extract_node(code, __name__) self.assertInferConst(ast[0], "") - for i in range(1, 16): + for i in range(1, 15): self.assertInferConst(ast[i], "") - for i in range(16, 19): + for i in range(15, 18): self.assertInferConst(ast[i], 0) def test_scope_lookup_same_attributes(self): @@ -2459,7 +2447,6 @@ def test_binary_op_type_errors(self): 1 ** (lambda x: x) #@ {} * {} #@ {} - {} #@ - {} | {} #@ {} >> {} #@ [] + () #@ () + [] #@ @@ -2504,7 +2491,6 @@ def __radd__(self, other): msg.format(op="**", lhs="int", rhs="function"), msg.format(op="*", lhs="dict", rhs="dict"), msg.format(op="-", lhs="dict", rhs="dict"), - msg.format(op="|", lhs="dict", rhs="dict"), msg.format(op=">>", lhs="dict", rhs="dict"), msg.format(op="+", lhs="list", rhs="tuple"), msg.format(op="+", lhs="tuple", rhs="list"), @@ -2519,6 +2505,12 @@ def __radd__(self, other): msg.format(op="+=", lhs="int", rhs="A"), msg.format(op="+=", lhs="int", rhs="list"), ] + + # PEP-584 supports | for dictionary union + if sys.version_info < (3, 9): + ast_nodes.append(extract_node("{} | {} #@")) + expected.append(msg.format(op="|", lhs="dict", rhs="dict")) + for node, expected_value in zip(ast_nodes, expected): errors = node.type_errors() self.assertEqual(len(errors), 1) @@ -3664,7 +3656,8 @@ def __getitem__(self, name): flow = AttributeDict() flow['app'] = AttributeDict() flow['app']['config'] = AttributeDict() - flow['app']['config']['doffing'] = AttributeDict() #@ + flow['app']['config']['doffing'] = AttributeDict() + flow['app']['config']['doffing']['thinkto'] = AttributeDict() #@ """ ) self.assertIsNone(helpers.safe_infer(ast_node.targets[0])) @@ -5667,6 +5660,10 @@ def test_custom_decorators_for_classmethod_and_staticmethods(code, obj, obj_type @pytest.mark.skipif(sys.version_info < (3, 8), reason="Needs dataclasses available") +@pytest.mark.skipif( + sys.version_info >= (3, 9), + reason="Exact inference with dataclasses (replace function) in python3.9", +) def test_dataclasses_subscript_inference_recursion_error(): code = """ from dataclasses import dataclass, replace @@ -5687,6 +5684,31 @@ class ProxyConfig: assert helpers.safe_infer(node) is None +@pytest.mark.skipif( + sys.version_info < (3, 9), + reason="Exact inference with dataclasses (replace function) in python3.9", +) +def test_dataclasses_subscript_inference_recursion_error_39(): + 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) + infer_val = helpers.safe_infer(node) + assert isinstance(infer_val, Instance) + assert infer_val.pytype() == ".ProxyConfig" + + def test_self_reference_infer_does_not_trigger_recursion_error(): # Prevents https://github.com/PyCQA/pylint/issues/1285 code = """ @@ -5863,5 +5885,19 @@ def test(self): assert list(inferred.nodes_of_class(nodes.Const)) == [] +def test_infer_list_of_uninferables_does_not_crash(): + code = """ + x = [A] * 1 + f = [x, [A] * 2] + x = list(f) + [] # List[Uninferable] + tuple(x[0]) + """ + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, nodes.Tuple) + # Would not be able to infer the first element. + assert not inferred.elts + + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_lookup.py b/tests/unittest_lookup.py index 84fd543302..bf30b7966f 100644 --- a/tests/unittest_lookup.py +++ b/tests/unittest_lookup.py @@ -1,6 +1,6 @@ # Copyright (c) 2007-2013 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2010 Daniel Harding -# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa +# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2019 Ashley Whetter diff --git a/tests/unittest_manager.py b/tests/unittest_manager.py index d7878b59fa..91a781c65e 100644 --- a/tests/unittest_manager.py +++ b/tests/unittest_manager.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2006, 2009-2014 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2013 AndroWiiid -# Copyright (c) 2014-2019 Claudiu Popa +# Copyright (c) 2014-2020 Claudiu Popa # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2017 Chris Philip diff --git a/tests/unittest_modutils.py b/tests/unittest_modutils.py index b5c41bf09b..9b3cecf4f2 100644 --- a/tests/unittest_modutils.py +++ b/tests/unittest_modutils.py @@ -10,6 +10,7 @@ # Copyright (c) 2019 Ashley Whetter # Copyright (c) 2019 Hugo van Kemenade # Copyright (c) 2019 markmcclain +# Copyright (c) 2020 Peter Kolbus # 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 @@ -82,7 +83,7 @@ def test_knownValues_load_module_from_name_2(self): def test_raise_load_module_from_name_1(self): self.assertRaises( - ImportError, modutils.load_module_from_name, "os.path", use_sys=0 + ImportError, modutils.load_module_from_name, "_this_module_does_not_exist_" ) @@ -297,6 +298,23 @@ def test_knownValues_is_relative_1(self): def test_knownValues_is_relative_3(self): self.assertFalse(modutils.is_relative("astroid", astroid.__path__[0])) + def test_deep_relative(self): + self.assertTrue(modutils.is_relative("ElementTree", xml.etree.__path__[0])) + + def test_deep_relative2(self): + self.assertFalse(modutils.is_relative("ElementTree", xml.__path__[0])) + + def test_deep_relative3(self): + self.assertTrue(modutils.is_relative("etree.ElementTree", xml.__path__[0])) + + def test_deep_relative4(self): + self.assertTrue(modutils.is_relative("etree.gibberish", xml.__path__[0])) + + def test_is_relative_bad_path(self): + self.assertFalse( + modutils.is_relative("ElementTree", os.path.join(xml.__path__[0], "ftree")) + ) + class GetModuleFilesTest(unittest.TestCase): def test_get_module_files_1(self): diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 5b6a39e3a4..d138ee1704 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -1360,5 +1360,16 @@ def paused_iter(iterable): assert bool(node.is_generator()) +def test_is_generator_for_yield_in_aug_assign(): + code = """ + def test(): + buf = '' + while True: + buf += yield + """ + node = astroid.extract_node(code) + assert bool(node.is_generator()) + + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_protocols.py b/tests/unittest_protocols.py index babff51eff..4f9bfbfc2e 100644 --- a/tests/unittest_protocols.py +++ b/tests/unittest_protocols.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015-2019 Claudiu Popa +# Copyright (c) 2015-2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2016 Jakub Wilk # Copyright (c) 2017 Łukasz Rogalski diff --git a/tests/unittest_python3.py b/tests/unittest_python3.py index b1759f03cd..db2d233a5e 100644 --- a/tests/unittest_python3.py +++ b/tests/unittest_python3.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2018 Claudiu Popa +# Copyright (c) 2013-2018, 2020 Claudiu Popa # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2016 Jared Garst diff --git a/tests/unittest_raw_building.py b/tests/unittest_raw_building.py index f782cdb2dc..160c88d775 100644 --- a/tests/unittest_raw_building.py +++ b/tests/unittest_raw_building.py @@ -1,5 +1,5 @@ # Copyright (c) 2013 AndroWiiid -# Copyright (c) 2014-2016, 2018-2019 Claudiu Popa +# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa # Copyright (c) 2014 Google, Inc. # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2018 Anthony Sottile diff --git a/tests/unittest_regrtest.py b/tests/unittest_regrtest.py index 17668edd23..1444da304e 100644 --- a/tests/unittest_regrtest.py +++ b/tests/unittest_regrtest.py @@ -93,7 +93,7 @@ def test_numpy_crash(self): data = """ from numpy import multiply -multiply(1, 2, 3) +multiply([1, 2], [3, 4]) """ astroid = builder.string_build(data, __name__, __file__) callfunc = astroid.body[1].value.func @@ -343,5 +343,16 @@ def format(self): assert isinstance(found[0], nodes.FunctionDef) +def test_crash_in_dunder_inference_prevented(): + code = """ + class MyClass(): + def fu(self, objects): + delitem = dict.__delitem__.__get__(self, dict) + delitem #@ + """ + inferred = next(extract_node(code).infer()) + assert "builtins.dict.__delitem__" == inferred.qname() + + if __name__ == "__main__": unittest.main() diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py index c4597fa686..b28605e654 100644 --- a/tests/unittest_scoped_nodes.py +++ b/tests/unittest_scoped_nodes.py @@ -19,6 +19,7 @@ # Copyright (c) 2019 Hugo van Kemenade # Copyright (c) 2019 Peter de Blanc # Copyright (c) 2019 hippo91 +# Copyright (c) 2020 Tim Martin # 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 @@ -1417,6 +1418,22 @@ def __init__(self): ], ) + def test_mro_with_attribute_classes(self): + cls = builder.extract_node( + """ + class A: + pass + class B: + pass + scope = object() + scope.A = A + scope.B = B + class C(scope.A, scope.B): + pass + """ + ) + self.assertEqualMro(cls, ["C", "A", "B", "object"]) + def test_generator_from_infer_call_result_parent(self): func = builder.extract_node( """ diff --git a/tests/unittest_transforms.py b/tests/unittest_transforms.py index 922f10f9ce..13d220a150 100644 --- a/tests/unittest_transforms.py +++ b/tests/unittest_transforms.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2018 Claudiu Popa +# Copyright (c) 2015-2018, 2020 Claudiu Popa # Copyright (c) 2015-2016 Ceridwen # Copyright (c) 2016 Jakub Wilk # Copyright (c) 2018 Bryce Guinta diff --git a/tests/unittest_utils.py b/tests/unittest_utils.py index 428026b1c4..1cc9afb653 100644 --- a/tests/unittest_utils.py +++ b/tests/unittest_utils.py @@ -1,6 +1,6 @@ # Copyright (c) 2008-2010, 2013 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015-2016, 2018 Claudiu Popa +# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa # Copyright (c) 2016 Ceridwen # Copyright (c) 2016 Dave Baum # Copyright (c) 2019 Ashley Whetter diff --git a/tox.ini b/tox.ini index 3ec33526ae..0e5d5a5a28 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35, py36, py37, py38, pypy, pylint +envlist = py35, py36, py37, py38, py39, pypy, pylint skip_missing_interpreters = true [testenv:pylint] @@ -18,14 +18,14 @@ deps = ; we have a brain for nose ; we use pytest for tests nose - py35,py36,py37: numpy - py35,py36,py37: attr - py35,py36,py37: typed_ast>=1.4.0,<1.5 + py35,py36,py37,py38,py39: numpy + py35,py36,py37,py38,py39: attr + py35,py36,py37,py38,py39: typed_ast>=1.4.0,<1.5 pytest python-dateutil pypy: singledispatch six~=1.12 - wrapt~=1.11 + wrapt>=1.11,<1.13 coverage<5 setenv =