diff options
-rw-r--r-- | astroid/bases.py | 31 | ||||
-rw-r--r-- | astroid/brain/brain_functools.py | 2 | ||||
-rw-r--r-- | astroid/inference.py | 21 | ||||
-rw-r--r-- | astroid/interpreter/objectmodel.py | 8 | ||||
-rw-r--r-- | astroid/scoped_nodes.py | 7 |
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): |