diff options
author | Claudiu Popa <pcmanticore@gmail.com> | 2020-03-06 10:46:26 +0100 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2020-03-06 10:54:48 +0100 |
commit | 17a5ee681bcf4aacffcc4ec5afbc3436cfdc4537 (patch) | |
tree | 10c691821e62b58c4c4a9efd5b29ef828306c24c /astroid/inference.py | |
parent | 88fd426e14c34cb5771fd6c06f5a1ba50bb03292 (diff) | |
download | astroid-git-17a5ee681bcf4aacffcc4ec5afbc3436cfdc4537.tar.gz |
Cache the inference of FunctionDef to prevent property inference mutating locals
When inferring a property, we instantiate a new `objects.Property` object,
which in turn, because it inherits from `FunctionDef`, sets itself in the locals
of the wrapping frame. This means that everytime we infer a property, the locals
are mutated with a new instance of the property.
Using `context` with `path_wrapper` would not have helped, because we call `inferred()`
on functions in multiple places in pylint's codebase.
Diffstat (limited to 'astroid/inference.py')
-rw-r--r-- | astroid/inference.py | 24 |
1 files changed, 23 insertions, 1 deletions
diff --git a/astroid/inference.py b/astroid/inference.py index 975b7d97..683f8609 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -25,6 +25,7 @@ import functools import itertools import operator +import wrapt from astroid import bases from astroid import context as contextmod from astroid import exceptions @@ -949,10 +950,30 @@ def infer_ifexp(self, context=None): nodes.IfExp._infer = infer_ifexp +# pylint: disable=dangerous-default-value +@wrapt.decorator +def _cached_generator(func, instance, args, kwargs, _cache={}): + node = args[0] + try: + return iter(_cache[func, id(node)]) + except KeyError: + result = func(*args, **kwargs) + # Need to keep an iterator around + original, copy = itertools.tee(result) + _cache[func, id(node)] = list(copy) + return original + + +# When inferring a property, we instantiate a new `objects.Property` object, +# which in turn, because it inherits from `FunctionDef`, sets itself in the locals +# of the wrapping frame. This means that everytime we infer a property, the locals +# are mutated with a new instance of the property. This is why we cache the result +# of the function's inference. +@_cached_generator def infer_functiondef(self, context=None): if not self.decorators or not bases._is_property(self): yield self - return + return dict(node=self, context=context) prop_func = objects.Property( function=self, @@ -964,6 +985,7 @@ def infer_functiondef(self, context=None): ) prop_func.postinit(body=[], args=self.args) yield prop_func + return dict(node=self, context=context) nodes.FunctionDef._infer = infer_functiondef |