1
0
forked from VimPlug/jedi
Files
jedi-fork/jedi/evaluate/param.py
2016-10-29 02:11:04 +02:00

467 lines
18 KiB
Python

from collections import defaultdict
from itertools import chain
from jedi._compatibility import unicode, zip_longest
from jedi import debug
from jedi import common
from jedi.parser import tree
from jedi.evaluate import iterable
from jedi.evaluate import analysis
from jedi.evaluate import precedence
def try_iter_content(types, depth=0):
"""Helper method for static analysis."""
if depth > 10:
# It's possible that a loop has references on itself (especially with
# CompiledObject). Therefore don't loop infinitely.
return
for typ in types:
try:
f = typ.py__iter__
except AttributeError:
pass
else:
for iter_types in f():
try_iter_content(iter_types, depth + 1)
class AbstractArguments(tree.Base):
def get_parent_until(self, *args, **kwargs):
raise DeprecationWarning
if self.trailer is None:
try:
element = self.argument_node[0]
if isinstance(element, iterable.AlreadyEvaluated):
element = list(self._evaluator.eval_element(self._context, element))[0]
except IndexError:
return None
else:
return element.get_parent_until(*args, **kwargs)
else:
return self.trailer.get_parent_until(*args, **kwargs)
def eval_argument_clinic(self, arguments):
"""Uses a list with argument clinic information (see PEP 436)."""
raise DeprecationWarning('not sure if we really deprecate it')
iterator = self.unpack()
for i, (name, optional, allow_kwargs) in enumerate(arguments):
key, va_values = next(iterator, (None, []))
if key is not None:
raise NotImplementedError
if not va_values and not optional:
debug.warning('TypeError: %s expected at least %s arguments, got %s',
name, len(arguments), i)
raise ValueError
values = set(chain.from_iterable(self._evaluator.eval_element(self._context, el)
for el in va_values))
if not values and not optional:
# For the stdlib we always want values. If we don't get them,
# that's ok, maybe something is too hard to resolve, however,
# we will not proceed with the evaluation of that function.
debug.warning('argument_clinic "%s" not resolvable.', name)
raise ValueError
yield values
def scope(self):
raise DeprecationWarning
# Returns the scope in which the arguments are used.
return (self.trailer or self.argument_node).get_parent_until(tree.IsScope)
def eval_args(self):
# TODO this method doesn't work with named args and a lot of other
# things. Use unpack.
raise DeprecationWarning
return [self._evaluator.eval_element(self._context, el) for stars, el in self._split()]
def eval_all(self, func=None):
"""
Evaluates all arguments as a support for static analysis
(normally Jedi).
"""
for key, element_values in self.unpack():
for element in element_values:
types = self._evaluator.eval_element(self._context, element)
try_iter_content(types)
class TreeArguments(AbstractArguments):
def __init__(self, evaluator, context, argument_node, trailer=None):
"""
The argument_node is either a parser node or a list of evaluated
objects. Those evaluated objects may be lists of evaluated objects
themselves (one list for the first argument, one for the second, etc).
:param argument_node: May be an argument_node or a list of nodes.
"""
self.argument_node = argument_node
self._context = context
self._evaluator = evaluator
self.trailer = trailer # Can be None, e.g. in a class definition.
def _split(self):
if isinstance(self.argument_node, (tuple, list)):
for el in self.argument_node:
yield 0, el
else:
if not (tree.is_node(self.argument_node, 'arglist') or (
# in python 3.5 **arg is an argument, not arglist
(tree.is_node(self.argument_node, 'argument') and
self.argument_node.children[0] in ('*', '**')))):
yield 0, self.argument_node
return
iterator = iter(self.argument_node.children)
for child in iterator:
if child == ',':
continue
elif child in ('*', '**'):
yield len(child.value), next(iterator)
elif tree.is_node(child, 'argument') and \
child.children[0] in ('*', '**'):
assert len(child.children) == 2
yield len(child.children[0].value), child.children[1]
else:
yield 0, child
def unpack(self, func=None):
named_args = []
for stars, el in self._split():
if stars == 1:
arrays = self._evaluator.eval_element(self._context, el)
iterators = [_iterate_star_args(self._evaluator, a, el, func)
for a in arrays]
iterators = list(iterators)
for values in list(zip_longest(*iterators)):
yield None, MergedLazyContexts(values)
elif stars == 2:
arrays = self._evaluator.eval_element(self._context, el)
dicts = [_star_star_dict(self._evaluator, a, el, func)
for a in arrays]
for dct in dicts:
for key, values in dct.items():
yield key, values
else:
if tree.is_node(el, 'argument'):
c = el.children
if len(c) == 3: # Keyword argument.
named_args.append((c[0].value, LazyContext(self._context, c[2]),))
else: # Generator comprehension.
# Include the brackets with the parent.
comp = iterable.GeneratorComprehension(
self._evaluator, self.argument_node.parent)
yield None, KnownContext(comp)
else:
yield None, LazyContext(self._context, el)
# Reordering var_args is necessary, because star args sometimes appear
# after named argument, but in the actual order it's prepended.
for named_arg in named_args:
yield named_arg
def as_tuple(self):
raise DeprecationWarning
for stars, argument in self._split():
if tree.is_node(argument, 'argument'):
argument, default = argument.children[::2]
else:
default = None
yield argument, default, stars
def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self.argument_node)
def get_calling_var_args(self):
return _get_calling_var_args(self._evaluator, self)
class KnownContext(object):
def __init__(self, value):
self._value = value
def infer(self):
return set([self._value])
class KnownContexts(object):
def __init__(self, values):
self._values = values
def infer(self):
return self._values
class UnknownContext(object):
def infer(self):
return set()
class LazyContext(object):
def __init__(self, context, node):
self._context = context
self._node = node
def infer(self):
return self._context.eval_node(self._node)
class MergedLazyContexts(object):
def __init__(self, lazy_contexts):
self._lazy_contexts = lazy_contexts
def infer(self):
return common.unite(l.infer() for l in self._lazy_contexts)
class ValueArguments(AbstractArguments):
def __init__(self, value_list):
self._value_list = value_list
def unpack(self, func=None):
for value in self._value_list:
yield None, KnownContext(value)
def get_calling_var_args(self):
return None
def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self._value_list)
class ExecutedParam(object):
"""Fake a param and give it values."""
def __init__(self, original_param, var_args, lazy_context):
assert not isinstance(lazy_context, (tuple, list))
self._original_param = original_param
self.var_args = var_args
self._lazy_context = lazy_context
self.string_name = self._original_param.name.value
def infer(self):
return self._lazy_context.infer()
@property
def position_nr(self):
# Need to use the original logic here, because it uses the parent.
return self._original_param.position_nr
def _get_calling_var_args(evaluator, var_args):
old_var_args = None
while var_args != old_var_args:
old_var_args = var_args
continue#TODO REMOVE
for name, default, stars in reversed(list(var_args.as_tuple())):
if not stars or not isinstance(name, tree.Name):
continue
names = evaluator.goto(name)
if len(names) != 1:
break
param = names[0].get_definition()
if not isinstance(param, ExecutedParam):
if isinstance(param, tree.Param):
# There is no calling var_args in this case - there's just
# a param without any input.
return None
break
# We never want var_args to be a tuple. This should be enough for
# now, we can change it later, if we need to.
if isinstance(param.var_args, Arguments):
var_args = param.var_args
return var_args.argument_node or var_args.trailer
def get_params(evaluator, parent_context, func, var_args):
result_params = []
param_dict = {}
for param in func.params:
param_dict[str(param.name)] = param
unpacked_va = list(var_args.unpack(func))
from jedi.evaluate.representation import InstanceElement
if isinstance(func, InstanceElement):
raise DeprecationWarning
# Include self at this place.
unpacked_va.insert(0, (None, [func.instance]))
var_arg_iterator = common.PushBackIterator(iter(unpacked_va))
non_matching_keys = defaultdict(lambda: [])
keys_used = {}
keys_only = False
had_multiple_value_error = False
for param in func.params:
# The value and key can both be null. There, the defaults apply.
# args / kwargs will just be empty arrays / dicts, respectively.
# Wrong value count is just ignored. If you try to test cases that are
# not allowed in Python, Jedi will maybe not show any completions.
default = None if param.default is None else LazyContext(parent_context, param.default)
key, argument = next(var_arg_iterator, (None, default))
while key is not None:
keys_only = True
k = unicode(key)
try:
key_param = param_dict[unicode(key)]
except KeyError:
non_matching_keys[key] = argument
else:
result_params.append(ExecutedParam(key_param, var_args, argument))
if k in keys_used:
had_multiple_value_error = True
m = ("TypeError: %s() got multiple values for keyword argument '%s'."
% (func.name, k))
calling_va = _get_calling_var_args(evaluator, var_args)
if calling_va is not None:
analysis.add(evaluator, 'type-error-multiple-values',
calling_va, message=m)
else:
try:
keys_used[k] = result_params[-1]
except IndexError:
# TODO this is wrong stupid and whatever.
pass
key, argument = next(var_arg_iterator, (None, None))
if param.stars == 1:
# *args param
values_list = []
if argument is not None:
values_list.append([argument])
for key, argument in var_arg_iterator:
# Iterate until a key argument is found.
if key:
var_arg_iterator.push_back((key, argument))
break
values_list.append([argument])
seq = iterable.FakeSequence(evaluator, 'tuple', values_list)
result_arg = KnownContext(seq)
elif param.stars == 2:
# **kwargs param
dct = iterable.FakeDict(evaluator, dict(non_matching_keys))
result_arg = KnownContext(dct)
non_matching_keys = {}
else:
# normal param
if argument is None:
# No value: Return an empty container
result_arg = UnknownContext()
if not keys_only:
calling_va = var_args.get_calling_var_args()
if calling_va is not None:
m = _error_argument_count(func, len(unpacked_va))
analysis.add(evaluator, 'type-error-too-few-arguments',
calling_va, message=m)
else:
result_arg = argument
# Now add to result if it's not one of the previously covered cases.
if (not keys_only or param.stars == 2):
result_params.append(ExecutedParam(param, var_args, result_arg))
keys_used[unicode(param.name)] = result_params[-1]
if keys_only:
# All arguments should be handed over to the next function. It's not
# about the values inside, it's about the names. Jedi needs to now that
# there's nothing to find for certain names.
for k in set(param_dict) - set(keys_used):
param = param_dict[k]
result_arg = (UnknownContext() if param.default is None else
LazyContext(parent_context, param.default))
result_params.append(ExecutedParam(param, var_args, result_arg))
if not (non_matching_keys or had_multiple_value_error or
param.stars or param.default):
# add a warning only if there's not another one.
calling_va = _get_calling_var_args(evaluator, var_args)
if calling_va is not None:
m = _error_argument_count(func, len(unpacked_va))
analysis.add(evaluator, 'type-error-too-few-arguments',
calling_va, message=m)
for key, argument in non_matching_keys.items():
m = "TypeError: %s() got an unexpected keyword argument '%s'." \
% (func.name, key)
analysis.add(evaluator, 'type-error-keyword-argument', argument.whatever, message=m)
remaining_params = list(var_arg_iterator)
if remaining_params:
m = _error_argument_count(func, len(unpacked_va))
# Just report an error for the first param that is not needed (like
# cPython).
first_key, first_values = remaining_params[0]
for v in first_values:
if first_key is not None:
# Is a keyword argument, return the whole thing instead of just
# the value node.
v = v.parent
try:
non_kw_param = keys_used[first_key]
except KeyError:
pass
else:
origin_args = non_kw_param.parent.var_args.argument_node
# TODO calculate the var_args tree and check if it's in
# the tree (if not continue).
# print('\t\tnonkw', non_kw_param.parent.var_args.argument_node, )
if origin_args not in [f.parent.parent for f in first_values]:
continue
analysis.add(evaluator, 'type-error-too-many-arguments',
v, message=m)
return result_params
def _iterate_star_args(evaluator, array, input_node, func=None):
from jedi.evaluate.representation import Instance
if isinstance(array, iterable.AbstractSequence):
raise DeprecationWarning('_items? seriously?')
# TODO ._items is not the call we want here. Replace in the future.
for node in array._items():
yield node
elif isinstance(array, iterable.Generator):
for types in array.py__iter__():
yield KnownContexts(types)
elif isinstance(array, Instance) and array.name.get_code() == 'tuple':
debug.warning('Ignored a tuple *args input %s' % array)
else:
if func is not None:
m = "TypeError: %s() argument after * must be a sequence, not %s" \
% (func.name.value, array)
analysis.add(evaluator, 'type-error-star', input_node, message=m)
def _star_star_dict(evaluator, array, input_node, func):
dct = defaultdict(lambda: [])
from jedi.evaluate.representation import Instance
if isinstance(array, Instance) and array.name.get_code() == 'dict':
# For now ignore this case. In the future add proper iterators and just
# make one call without crazy isinstance checks.
return {}
if isinstance(array, iterable.FakeDict):
return array._dct
elif isinstance(array, iterable.Array) and array.type == 'dict':
# TODO bad call to non-public API
for key_node, value in array._items():
for key in evaluator.eval_element(key_node):
if precedence.is_string(key):
dct[key.obj].append(value)
else:
if func is not None:
m = "TypeError: %s argument after ** must be a mapping, not %s" \
% (func.name.value, array)
analysis.add(evaluator, 'type-error-star-star', input_node, message=m)
return dict(dct)
def _error_argument_count(func, actual_count):
default_arguments = sum(1 for p in func.params if p.default or p.stars)
if default_arguments == 0:
before = 'exactly '
else:
before = 'from %s to ' % (len(func.params) - default_arguments)
return ('TypeError: %s() takes %s%s arguments (%s given).'
% (func.name, before, len(func.params), actual_count))