Skip to content

Commit

Permalink
Fix inference issue with inference path cloning
Browse files Browse the repository at this point in the history
Copy inference path in inference context upon cloning to prevent diverging inference paths causing uninferable results
  • Loading branch information
brycepg committed Feb 19, 2018
1 parent 387f988 commit 206d8a2
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 2 deletions.
33 changes: 32 additions & 1 deletion astroid/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,43 @@
"""Various context related utilities, including inference and call contexts."""

import contextlib
import copy
import pprint


class InferenceContext(object):
"""Provide context for inference
Store already inferred nodes to save time
Account for already visited nodes to infinite stop infinite recursion
"""

__slots__ = ('path', 'lookupname', 'callcontext', 'boundnode', 'inferred')

def __init__(self, path=None, inferred=None):
self.path = path or set()
"""Path of visited nodes and their lookupname
:type: set(tuple(NodeNG, optional(str)))"""
self.lookupname = None
self.callcontext = None
self.boundnode = None
self.inferred = inferred or {}
"""
:type: dict(seq, seq)
Inferred node contexts to their mapped results
Currently the key is (node, lookupname, callcontext, boundnode)
and the value is tuple of the inferred results
"""

def push(self, node):
"""Push node into inference path
:return: True if node is already in context path else False
:rtype: bool
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:
return True
Expand All @@ -29,13 +52,21 @@ def push(self, node):
return False

def clone(self):
"""Clone inference path
For example, each side of a binary operation (BinOp)
starts with the same context but diverge as each side is inferred
so the InferenceContext will need be cloned"""
# XXX copy lookupname/callcontext ?
clone = InferenceContext(self.path, inferred=self.inferred)
clone = InferenceContext(copy.copy(self.path), inferred=self.inferred)
clone.callcontext = self.callcontext
clone.boundnode = self.boundnode
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)
Expand Down
6 changes: 5 additions & 1 deletion astroid/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ def __get__(self, inst, objtype=None):


def path_wrapper(func):
"""return the given infer function wrapped to handle the path"""
"""return the given infer function wrapped to handle the path
Used to stop inference if the node has already been looked
at for a given `InferenceContext` to prevent infinite recursion
"""
# TODO: switch this to wrapt after the monkey-patching is fixed (ceridwen)
@functools.wraps(func)
def wrapped(node, context=None, _func=func, **kwargs):
Expand Down

0 comments on commit 206d8a2

Please sign in to comment.