summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--astroid/bases.py31
-rw-r--r--astroid/brain/brain_functools.py2
-rw-r--r--astroid/inference.py21
-rw-r--r--astroid/interpreter/objectmodel.py8
-rw-r--r--astroid/scoped_nodes.py7
5 files changed, 53 insertions, 16 deletions
diff --git a/astroid/bases.py b/astroid/bases.py
index 3b6a248a..64df70dd 100644
--- a/astroid/bases.py
+++ b/astroid/bases.py
@@ -203,7 +203,7 @@ class BaseInstance(Proxy):
else:
yield attr
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
"""infer what a class instance is returning when called"""
inferred = False
for node in self._proxied.igetattr('__call__', context):
@@ -302,14 +302,33 @@ class UnboundMethod(Proxy):
return iter((self.special_attributes.lookup(name), ))
return self._proxied.igetattr(name, context)
- def infer_call_result(self, caller, context):
+ def infer_call_result(self, caller, context, context_lookup=None):
+ """
+ The context_lookup argument is used to correctly infer
+ arguments to object.__new__(cls) calls inside classmethods
+
+ The boundnode of the regular context with a function called
+ on ``object.__new__`` will be of type ``object``,
+ which is incorrect for the argument in general.
+ If no context is given the ``object.__new__`` call argument will
+ correctly inferred except when inside a call that requires
+ the additonal context (such as a classmethod) of the boundnode
+ to determine which class the method was called from
+ """
+
# If we're unbound method __new__ of builtin object, the result is an
# instance of the class given as first argument.
if (self._proxied.name == '__new__' and
self._proxied.parent.frame().qname() == '%s.object' % BUILTINS):
- infer = caller.args[0].infer() if caller.args else []
+ if caller.args:
+ if context_lookup is None:
+ context_lookup = {}
+ node_context = context_lookup.get(caller.args[0])
+ infer = caller.args[0].infer(context=node_context)
+ else:
+ infer = []
return (Instance(x) if x is not util.Uninferable else x for x in infer)
- return self._proxied.infer_call_result(caller, context)
+ return self._proxied.infer_call_result(caller, context, context_lookup)
def bool_value(self):
return True
@@ -394,7 +413,7 @@ class BoundMethod(UnboundMethod):
cls.locals = cls_locals
return cls
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
if context is None:
context = contextmod.InferenceContext()
context = context.clone()
@@ -412,7 +431,7 @@ class BoundMethod(UnboundMethod):
if new_cls:
return iter((new_cls, ))
- return super(BoundMethod, self).infer_call_result(caller, context)
+ return super(BoundMethod, self).infer_call_result(caller, context, context_lookup)
def bool_value(self):
return True
diff --git a/astroid/brain/brain_functools.py b/astroid/brain/brain_functools.py
index 6dcf7cd9..d76405ee 100644
--- a/astroid/brain/brain_functools.py
+++ b/astroid/brain/brain_functools.py
@@ -31,7 +31,7 @@ class LruWrappedModel(objectmodel.FunctionModel):
_CacheInfo(0, 0, 0, 0)
''')
class CacheInfoBoundMethod(BoundMethod):
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
yield helpers.safe_infer(cache_info)
return CacheInfoBoundMethod(proxy=self._instance, bound=self._instance)
diff --git a/astroid/inference.py b/astroid/inference.py
index 37b1b2bb..0a55b6e8 100644
--- a/astroid/inference.py
+++ b/astroid/inference.py
@@ -187,13 +187,15 @@ def infer_call(self, context=None):
callcontext.callcontext = contextmod.CallContext(args=self.args,
keywords=self.keywords)
callcontext.boundnode = None
+ if context is not None:
+ context_lookup = _populate_context_lookup(self, context.clone())
for callee in self.func.infer(context):
if callee is util.Uninferable:
yield callee
continue
try:
if hasattr(callee, 'infer_call_result'):
- for inferred in callee.infer_call_result(self, callcontext):
+ for inferred in callee.infer_call_result(self, callcontext, context_lookup):
yield inferred
except exceptions.InferenceError:
## XXX log error ?
@@ -832,3 +834,20 @@ def instance_getitem(self, index, context=None):
node=self, index=index, context=context))
bases.Instance.getitem = instance_getitem
+
+
+def _populate_context_lookup(call, context):
+ # Allows context to be saved for later
+ # for inference inside a function
+ context_lookup = {}
+ if context is None:
+ return context_lookup
+ for arg in call.args:
+ if isinstance(arg, nodes.Starred):
+ context_lookup[arg.value] = context
+ else:
+ context_lookup[arg] = context
+ keywords = call.keywords if call.keywords is not None else []
+ for keyword in keywords:
+ context_lookup[keyword.value] = context
+ return context_lookup
diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py
index bae60fc5..b1a1abe5 100644
--- a/astroid/interpreter/objectmodel.py
+++ b/astroid/interpreter/objectmodel.py
@@ -288,7 +288,7 @@ class FunctionModel(ObjectModel):
class DescriptorBoundMethod(bases.BoundMethod):
"""Bound method which knows how to understand calling descriptor binding."""
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
if len(caller.args) != 2:
raise exceptions.InferenceError(
"Invalid arguments for descriptor binding",
@@ -382,7 +382,7 @@ class ClassModel(ObjectModel):
# Cls.mro is a method and we need to return one in order to have a proper inference.
# The method we're returning is capable of inferring the underlying MRO though.
class MroBoundMethod(bases.BoundMethod):
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
yield other_self.py__mro__
implicit_metaclass = self._instance.implicit_metaclass()
@@ -425,7 +425,7 @@ class ClassModel(ObjectModel):
obj.postinit(classes)
class SubclassesBoundMethod(bases.BoundMethod):
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
yield obj
implicit_metaclass = self._instance.implicit_metaclass()
@@ -558,7 +558,7 @@ class DictModel(ObjectModel):
"""Generate a bound method that can infer the given *obj*."""
class DictMethodBoundMethod(astroid.BoundMethod):
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
yield obj
meth = next(self._instance._proxied.igetattr(name))
diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py
index dabda60c..31494adc 100644
--- a/astroid/scoped_nodes.py
+++ b/astroid/scoped_nodes.py
@@ -1150,7 +1150,7 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
names.append(self.args.kwarg)
return names
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
"""Infer what the function returns when called.
:param caller: Unused
@@ -1159,7 +1159,6 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
# pylint: disable=no-member; github.com/pycqa/astroid/issues/291
# args is in fact redefined later on by postinit. Can't be changed
# to None due to a strong interaction between Lambda and FunctionDef.
-
return self.body.infer(context)
def scope_lookup(self, node, name, offset=0):
@@ -1537,7 +1536,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
"""
return next(self._get_yield_nodes_skip_lambdas(), False)
- def infer_call_result(self, caller=None, context=None):
+ def infer_call_result(self, caller=None, context=None, context_lookup=None):
"""Infer what the function returns when called.
:returns: What the function returns.
@@ -1990,7 +1989,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
result.parent = caller.parent
return result
- def infer_call_result(self, caller, context=None):
+ def infer_call_result(self, caller, context=None, context_lookup=None):
"""infer what a class is returning when called"""
if (self.is_subtype_of('%s.type' % (BUILTINS,), context)
and len(caller.args) == 3):