summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--astroid/manager.py2
-rw-r--r--astroid/node_classes.py4
-rw-r--r--astroid/tests/unittest_inference.py30
-rw-r--r--astroid/util.py24
5 files changed, 65 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index cd4d76ee..211fbb75 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -157,6 +157,12 @@ Release Date: Unknown
Close PyCQA/pylint#2159
+ * Limit the maximum amount of interable result in an NodeNG.infer() call to
+ 100 by default for performance issues with variables with large amounts of
+ possible values.
+ The max inferable value can be tuned by setting the ASTROID_MAX_INFERABLE environment
+ variable at start up.
+
What's New in astroid 1.6.0?
============================
diff --git a/astroid/manager.py b/astroid/manager.py
index c1caf151..c7553661 100644
--- a/astroid/manager.py
+++ b/astroid/manager.py
@@ -52,6 +52,8 @@ class AstroidManager:
# Export these APIs for convenience
self.register_transform = self._transform.register_transform
self.unregister_transform = self._transform.unregister_transform
+ self.max_inferable = int(
+ os.environ.get("ASTROID_MAX_INFERABLE", 100))
def visit_transforms(self, node):
"""Visit the transforms and apply them to the given *node*."""
diff --git a/astroid/node_classes.py b/astroid/node_classes.py
index a17af1a2..a3c02fcf 100644
--- a/astroid/node_classes.py
+++ b/astroid/node_classes.py
@@ -323,7 +323,9 @@ class NodeNG:
if key in context.inferred:
return iter(context.inferred[key])
- return context.cache_generator(key, self._infer(context, **kwargs))
+ gen = context.cache_generator(
+ key, self._infer(context, **kwargs))
+ return util.limit_inference(gen, MANAGER.max_inferable)
def _repr_name(self):
"""Get a name for nice representation.
diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py
index 5ee53d1f..5f9194c3 100644
--- a/astroid/tests/unittest_inference.py
+++ b/astroid/tests/unittest_inference.py
@@ -15,6 +15,7 @@ import platform
import sys
from functools import partial
import unittest
+from unittest.mock import patch
import pytest
@@ -4656,5 +4657,34 @@ class TestInferencePropagation:
assert next(extract_node(code).infer()).as_string() == "{'f': 1}"
+def test_limit_inference_result_amount():
+ """Test setting limit inference result amount"""
+ code = """
+ args = []
+
+ if True:
+ args += ['a']
+
+ if True:
+ args += ['b']
+
+ if True:
+ args += ['c']
+
+ if True:
+ args += ['d']
+
+ args #@
+ """
+ result = extract_node(code).inferred()
+ assert len(result) == 16
+ with patch('astroid.node_classes.MANAGER.max_inferable', 4):
+ result_limited = extract_node(code).inferred()
+ # Can't guarentee exact size
+ assert len(result_limited) < 16
+ # Will not always be at the end
+ assert util.Uninferable in result_limited
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/astroid/util.py b/astroid/util.py
index a5310970..8497b34a 100644
--- a/astroid/util.py
+++ b/astroid/util.py
@@ -5,6 +5,7 @@
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
import warnings
+from itertools import islice
import importlib
import lazy_object_proxy
@@ -126,5 +127,28 @@ 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
+
+
# Backwards-compatibility aliases
YES = Uninferable