Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e55f105
Temporary debug statement + first correction of the bug 2588 by avoid…
hippo91 Dec 2, 2018
16eb616
Merge remote-tracking branch 'origin/master' into bug_pylint_2588
hippo91 Dec 8, 2018
1f7dd68
Merge branch 'master' into bug_pylint_2588_bis
hippo91 Dec 15, 2018
f847792
Fix the pylint's bug 2588 by avoiding statement deletion in the _filt…
hippo91 Dec 15, 2018
7400abb
Reactivation of the unittests dedicated to brain_functools. Add of a …
hippo91 Dec 15, 2018
c5da3d2
Suppress a debugger statement
hippo91 Dec 15, 2018
151217c
Add of ChangeLog entry
hippo91 Dec 15, 2018
e4e4a49
Merge branch 'master' into bug_pylint_2588_bis
hippo91 Dec 21, 2018
67a32eb
Removing unittest.TestCase inheritance for TestFunctoolsPartial
hippo91 Dec 22, 2018
362832f
Extracting the class PartialFunction from brain_functools and inserti…
hippo91 Dec 22, 2018
66c413e
Reformating with black
hippo91 Dec 22, 2018
f6cbb1a
Finalzing the test case
hippo91 Dec 22, 2018
b3fa94a
Just breakpoint to debug and mark the origin of the problem
hippo91 Dec 22, 2018
b85b6da
Revert "Just breakpoint to debug and mark the origin of the problem"
hippo91 Dec 22, 2018
b1937b9
Merge branch 'master' of https://github.com/PyCQA/astroid
hippo91 Jan 3, 2019
f13c51b
Merge branch 'master' into bug_pylint_2588_bis
hippo91 Jan 3, 2019
e1c899a
Correcting formatting according to black 18.6b4
hippo91 Jan 3, 2019
fd3cdad
Merge branch 'master' of https://github.com/PyCQA/astroid
hippo91 Jan 20, 2019
9c17b23
Merge branch 'master' into bug_pylint_2588_bis
hippo91 Jan 20, 2019
99eabe2
Merge branch 'master' of https://github.com/PyCQA/astroid
hippo91 Jan 21, 2019
bbc41b0
Merge branch 'master' into bug_pylint_2588_bis
hippo91 Jan 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ What's New in astroid 2.2.0?
============================
Release Date: TBA

* Fix a bug where a call to a function that has been previously called via functools.partial was wrongly inferred

Close PyCQA/pylint#2588

* Fix a recursion error caused by inferring the ``slice`` builtin.

Expand Down
28 changes: 3 additions & 25 deletions astroid/brain/brain_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from astroid import helpers
from astroid.interpreter import objectmodel
from astroid import MANAGER
from astroid import objects


LRU_CACHE = "functools.lru_cache"
Expand Down Expand Up @@ -98,31 +99,8 @@ def _functools_partial_inference(node, context=None):
"wrapped function received unknown parameters"
)

# Return a wrapped() object that can be used further for inference
class PartialFunction(astroid.FunctionDef):

filled_positionals = len(call.positional_arguments[1:])
filled_keywords = list(call.keyword_arguments)

def infer_call_result(self, caller=None, context=None):
nonlocal call
filled_args = call.positional_arguments[1:]
filled_keywords = call.keyword_arguments

if context:
current_passed_keywords = {
keyword for (keyword, _) in context.callcontext.keywords
}
for keyword, value in filled_keywords.items():
if keyword not in current_passed_keywords:
context.callcontext.keywords.append((keyword, value))

call_context_args = context.callcontext.args or []
context.callcontext.args = filled_args + call_context_args

return super().infer_call_result(caller=caller, context=context)

partial_function = PartialFunction(
partial_function = objects.PartialFunction(
call,
name=inferred_wrapped_function.name,
doc=inferred_wrapped_function.doc,
lineno=inferred_wrapped_function.lineno,
Expand Down
12 changes: 10 additions & 2 deletions astroid/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1217,8 +1217,16 @@ def _filter_stmts(self, stmts, frame, offset):
# want to clear previous assignments if any (hence the test on
# optional_assign)
if not (optional_assign or are_exclusive(_stmts[pindex], node)):
del _stmt_parents[pindex]
del _stmts[pindex]
if (
# In case of partial function node, if the statement is different
# from the origin function then it can be deleted otherwise it should
# remain to be able to correctly infer the call to origin function.
not node.is_function
or node.qname() != "PartialFunction"
or node.name != _stmts[pindex].name
):
del _stmt_parents[pindex]
del _stmts[pindex]
if isinstance(node, AssignName):
if not optional_assign and stmt.parent is mystmt.parent:
_stmts = []
Expand Down
29 changes: 29 additions & 0 deletions astroid/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,35 @@ class DictValues(bases.Proxy):
__repr__ = node_classes.NodeNG.__repr__


class PartialFunction(scoped_nodes.FunctionDef):
"""A class representing partial function obtained via functools.partial"""

def __init__(
self, call, name=None, doc=None, lineno=None, col_offset=None, parent=None
):
super().__init__(name, doc, lineno, col_offset, parent)
self.filled_positionals = len(call.positional_arguments[1:])
self.filled_args = call.positional_arguments[1:]
self.filled_keywords = call.keyword_arguments

def infer_call_result(self, caller=None, context=None):
if context:
current_passed_keywords = {
keyword for (keyword, _) in context.callcontext.keywords
}
for keyword, value in self.filled_keywords.items():
if keyword not in current_passed_keywords:
context.callcontext.keywords.append((keyword, value))

call_context_args = context.callcontext.args or []
context.callcontext.args = self.filled_args + call_context_args

return super().infer_call_result(caller=caller, context=context)

def qname(self):
return self.__class__.__name__


# TODO: Hack to solve the circular import problem between node_classes and objects
# This is not needed in 2.0, which has a cleaner design overall
node_classes.Dict.__bases__ = (node_classes.NodeNG, DictInstance)
5 changes: 4 additions & 1 deletion astroid/tests/unittest_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1776,9 +1776,12 @@ def other_test(a, b, *, c=1):
partial(other_test, c=4)(1, 3) #@
partial(other_test, 4, c=4)(4) #@
partial(other_test, 4, c=4)(b=5) #@
test(1, 2) #@
partial(other_test, 1, 2)(c=3) #@
partial(test, b=4)(a=3) #@
"""
)
expected_values = [4, 7, 7, 3, 12, 16, 32, 36]
expected_values = [4, 7, 7, 3, 12, 16, 32, 36, 3, 9, 7]
for node, expected_value in zip(ast_nodes, expected_values):
inferred = next(node.infer())
assert isinstance(inferred, astroid.Const)
Expand Down