summaryrefslogtreecommitdiff
path: root/astroid/context.py
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2015-08-12 20:59:36 +0300
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2015-08-12 20:59:36 +0300
commit7ec07a045399be5937465b65a81239b6ef3f8036 (patch)
tree645f79014628d18736a5d43d08006efbb12c28f3 /astroid/context.py
parenta6721e299db6f4bb0f6b59416f9cb0ad417c77ff (diff)
downloadastroid-git-7ec07a045399be5937465b65a81239b6ef3f8036.tar.gz
Move InferenceContext and CallContext into astroid.context
In order to reduce circular dependencies between components, CallContext is moved into a new module, astroid.context. At the same time, for increasing the cohesion inside astroid.bases, InferenceContext was moved as well into astroid.context.
Diffstat (limited to 'astroid/context.py')
-rw-r--r--astroid/context.py141
1 files changed, 141 insertions, 0 deletions
diff --git a/astroid/context.py b/astroid/context.py
new file mode 100644
index 00000000..3e420d76
--- /dev/null
+++ b/astroid/context.py
@@ -0,0 +1,141 @@
+# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of astroid.
+#
+# astroid is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 2.1 of the License, or (at your
+# option) any later version.
+#
+# astroid is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with astroid. If not, see <http://www.gnu.org/licenses/>.
+
+"""Various context related utilities, including inference and call contexts."""
+
+import contextlib
+import itertools
+
+from astroid import exceptions
+from astroid import util
+
+
+class InferenceContext(object):
+ __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode', 'infered')
+
+ def __init__(self, path=None, infered=None):
+ self.path = path or set()
+ self.lookupname = None
+ self.callcontext = None
+ self.boundnode = None
+ self.infered = infered or {}
+
+ def push(self, node):
+ name = self.lookupname
+ if (node, name) in self.path:
+ raise StopIteration()
+ self.path.add((node, name))
+
+ def clone(self):
+ # XXX copy lookupname/callcontext ?
+ clone = InferenceContext(self.path, infered=self.infered)
+ clone.callcontext = self.callcontext
+ clone.boundnode = self.boundnode
+ return clone
+
+ def cache_generator(self, key, generator):
+ results = []
+ for result in generator:
+ results.append(result)
+ yield result
+
+ self.infered[key] = tuple(results)
+ return
+
+ @contextlib.contextmanager
+ def restore_path(self):
+ path = set(self.path)
+ yield
+ self.path = path
+
+
+class CallContext(object):
+
+ def __init__(self, args, keywords=None, starargs=None, kwargs=None):
+ self.args = args
+ if keywords:
+ self.keywords = {arg.arg: arg.value for arg in keywords}
+ else:
+ self.keywords = {}
+
+ self.starargs = starargs
+ self.kwargs = kwargs
+
+ @staticmethod
+ def _infer_argument_container(container, key, context):
+ its = []
+ for infered in container.infer(context=context):
+ if infered is util.YES:
+ its.append((util.YES,))
+ continue
+ try:
+ its.append(infered.getitem(key, context).infer(context=context))
+ except (exceptions.InferenceError, AttributeError):
+ its.append((util.YES,))
+ except (IndexError, TypeError):
+ continue
+ if its:
+ return itertools.chain(*its)
+
+ def infer_argument(self, funcnode, name, context, boundnode):
+ """infer a function argument value according to the call context"""
+ # 1. search in named keywords
+ try:
+ return self.keywords[name].infer(context)
+ except KeyError:
+ pass
+
+ argindex = funcnode.args.find_argname(name)[0]
+ if argindex is not None:
+ # 2. first argument of instance/class method
+ if argindex == 0 and funcnode.type in ('method', 'classmethod'):
+ return iter((boundnode,))
+ # if we have a method, extract one position
+ # from the index, so we'll take in account
+ # the extra parameter represented by `self` or `cls`
+ if funcnode.type in ('method', 'classmethod'):
+ argindex -= 1
+ # 2. search arg index
+ try:
+ return self.args[argindex].infer(context)
+ except IndexError:
+ pass
+ # 3. search in *args (.starargs)
+ if self.starargs is not None:
+ its = self._infer_argument_container(
+ self.starargs, argindex, context)
+ if its:
+ return its
+ # 4. Search in **kwargs
+ if self.kwargs is not None:
+ its = self._infer_argument_container(
+ self.kwargs, name, context)
+ if its:
+ return its
+ # 5. return default value if any
+ try:
+ return funcnode.args.default_value(name).infer(context)
+ except exceptions.NoDefault:
+ raise exceptions.InferenceError(name)
+
+
+def copy_context(context):
+ if context is not None:
+ return context.clone()
+ else:
+ return InferenceContext()