From a28b179a45c90649733abf3b7c0a0fd95e46c61c Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 2 Sep 2018 19:12:13 +0200 Subject: [PATCH] Fix partial --- jedi/plugins/stdlib.py | 51 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 14d71ffa..8cd6691f 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -14,14 +14,16 @@ import parso from jedi._compatibility import force_unicode from jedi.plugins.base import BasePlugin from jedi import debug -from jedi.evaluate.arguments import ValuesArguments, repack_with_argument_clinic +from jedi.evaluate.arguments import ValuesArguments, \ + repack_with_argument_clinic, AbstractArguments from jedi.evaluate import analysis from jedi.evaluate import compiled from jedi.evaluate.context.instance import \ AbstractInstanceContext, CompiledInstance, BoundMethod, InstanceArguments from jedi.evaluate.base_context import ContextualizedNode, \ NO_CONTEXTS, ContextSet -from jedi.evaluate.context import ClassContext, ModuleContext, FunctionExecutionContext +from jedi.evaluate.context import ClassContext, ModuleContext, \ + FunctionExecutionContext from jedi.evaluate.context import iterable from jedi.evaluate.lazy_context import LazyTreeContext from jedi.evaluate.syntax_tree import is_string @@ -295,6 +297,48 @@ def collections_namedtuple(evaluator, obj, arguments): return ContextSet(ClassContext(evaluator, parent_context, generated_class)) +class PartialObject(object): + def __init__(self, actual_context, arguments): + self._actual_context = actual_context + self._arguments = arguments + + def __getattr__(self, name): + return getattr(self._actual_context, name) + + def py__call__(self, arguments): + key, lazy_context = next(self._arguments.unpack(), (None, None)) + if key is not None or lazy_context is None: + debug.warning("Partial should have a proper function %s", self._arguments) + return NO_CONTEXTS + + return lazy_context.infer().execute( + MergedPartialArguments(self._arguments, arguments) + ) + + +class MergedPartialArguments(AbstractArguments): + def __init__(self, partial_arguments, call_arguments): + self._partial_arguments = partial_arguments + self._call_arguments = call_arguments + + def unpack(self, funcdef=None): + unpacked = self._partial_arguments.unpack(funcdef) + # Ignore this one, it's the function. It was checked before that it's + # there. + next(unpacked) + for key_lazy_context in unpacked: + yield key_lazy_context + for key_lazy_context in self._call_arguments.unpack(funcdef): + yield key_lazy_context + + +def functools_partial(evaluator, obj, arguments): + return ContextSet.from_iterable( + PartialObject(instance, arguments) + for instance in obj.py__call__(arguments) + ) + + @argument_clinic('first, /') def _return_first_param(evaluator, firsts): return firsts @@ -319,6 +363,9 @@ _implemented = { 'collections': { 'namedtuple': collections_namedtuple, }, + 'functools': { + 'partial': functools_partial, + }, 'abc': { # Not sure if this is necessary, but it's used a lot in typeshed and # it's for now easier to just pass the function.