1
0
forked from VimPlug/jedi

Fix param/argument static analysis.

This commit is contained in:
Dave Halter
2016-12-15 00:25:10 +01:00
parent 6c4abcc84c
commit 7084d9ab89
3 changed files with 56 additions and 60 deletions

View File

@@ -101,9 +101,9 @@ class ParamName(AbstractTreeName):
self.tree_name = tree_name
def infer(self):
return self._get_param().infer()
return self.get_param().infer()
def _get_param(self):
def get_param(self):
params = self.parent_context.get_params()
param_node = search_ancestor(self.tree_name, 'param')
return params[param_node.position_nr]
@@ -117,7 +117,7 @@ class AnonymousInstanceParamName(ParamName):
# it's known). This only affects anonymous instances.
return set([self.parent_context.instance])
else:
return self._get_param().infer()
return self.get_param().infer()
class AbstractFilter(object):

View File

@@ -431,8 +431,8 @@ class InstanceVarArgs(object):
for values in self._get_var_args().unpack(func):
yield values
def get_calling_var_args(self):
return self._get_var_args().get_calling_var_args()
def get_calling_nodes(self):
return self._get_var_args().get_calling_nodes()
class InstanceFunctionExecution(er.FunctionExecutionContext):

View File

@@ -9,6 +9,7 @@ from jedi.evaluate import analysis
from jedi.evaluate import context
from jedi.evaluate import docstrings
from jedi.evaluate import pep0484
from jedi.evaluate.filters import ParamName
def add_argument_issue(parent_context, error_name, lazy_context, message):
@@ -60,12 +61,6 @@ class AbstractArguments():
raise ValueError
yield values
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
@@ -120,7 +115,7 @@ class TreeArguments(AbstractArguments):
for stars, el in self._split():
if stars == 1:
arrays = self.context.eval_node(el)
iterators = [_iterate_star_args(self._evaluator, a, el, func)
iterators = [_iterate_star_args(self.context, a, el, func)
for a in arrays]
iterators = list(iterators)
for values in list(zip_longest(*iterators)):
@@ -132,7 +127,7 @@ class TreeArguments(AbstractArguments):
elif stars == 2:
arrays = self._evaluator.eval_element(self.context, el)
for dct in arrays:
for key, values in _star_star_dict(self._evaluator, dct, el, func):
for key, values in _star_star_dict(self.context, dct, el, func):
yield key, values
else:
if tree.is_node(el, 'argument'):
@@ -152,8 +147,7 @@ class TreeArguments(AbstractArguments):
for named_arg in named_args:
yield named_arg
def as_tuple(self):
raise DeprecationWarning
def as_tree_tuple_objects(self):
for stars, argument in self._split():
if tree.is_node(argument, 'argument'):
argument, default = argument.children[::2]
@@ -164,8 +158,36 @@ class TreeArguments(AbstractArguments):
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
def get_calling_var_args(self):
return _get_calling_var_args(self._evaluator, self)
def get_calling_nodes(self):
from jedi.evaluate.dynamic import MergedExecutedParams
old_arguments_list = []
arguments = self
while arguments not in old_arguments_list:
if not isinstance(arguments, TreeArguments):
break
old_arguments_list.append(arguments)
for name, default, stars in reversed(list(arguments.as_tree_tuple_objects())):
if not stars or not isinstance(name, tree.Name):
continue
names = self._evaluator.goto(arguments.context, name)
if len(names) != 1:
break
if not isinstance(names[0], ParamName):
break
param = names[0].get_param()
if isinstance(param, MergedExecutedParams):
# For dynamic searches we don't even want to see errors.
return []
if not isinstance(param, ExecutedParam):
break
if param.var_args is None:
break
arguments = param.var_args
return [arguments.argument_node or arguments.trailer]
class ValuesArguments(AbstractArguments):
@@ -176,8 +198,8 @@ class ValuesArguments(AbstractArguments):
for values in self._values_list:
yield None, context.LazyKnownContexts(values)
def get_calling_var_args(self):
return None
def get_calling_nodes(self):
return []
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._values_list)
@@ -209,32 +231,6 @@ class ExecutedParam(object):
return '<%s: %s>' % (self.__class__.__name__, self.string_name)
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 = {}
@@ -264,10 +260,9 @@ def get_params(evaluator, parent_context, func, var_args):
had_multiple_value_error = True
m = ("TypeError: %s() got multiple values for keyword argument '%s'."
% (func.name, key))
calling_va = _get_calling_var_args(evaluator, var_args)
if calling_va is not None:
for node in var_args.get_calling_nodes():
analysis.add(parent_context, 'type-error-multiple-values',
calling_va, message=m)
node, message=m)
else:
keys_used[key] = ExecutedParam(parent_context, key_param, var_args, argument)
key, argument = next(var_arg_iterator, (None, None))
@@ -303,17 +298,17 @@ def get_params(evaluator, parent_context, func, var_args):
if param.default is None:
result_arg = context.LazyUnknownContext()
if not keys_only:
calling_va = var_args.get_calling_var_args()
if calling_va is not None:
for node in var_args.get_calling_nodes():
m = _error_argument_count(func, len(unpacked_va))
analysis.add(parent_context, 'type-error-too-few-arguments',
calling_va, message=m)
node, message=m)
else:
result_arg = context.LazyTreeContext(parent_context, param.default)
else:
result_arg = argument
result_params.append(ExecutedParam(parent_context, param, var_args, result_arg))
if not isinstance(result_arg, context.LazyUnknownContext):
keys_used[param.name.value] = result_params[-1]
if keys_only:
@@ -326,11 +321,10 @@ def get_params(evaluator, parent_context, func, var_args):
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:
for node in var_args.get_calling_nodes():
m = _error_argument_count(func, len(unpacked_va))
analysis.add(parent_context, 'type-error-too-few-arguments',
calling_va, message=m)
node, message=m)
for key, lazy_context in non_matching_keys.items():
m = "TypeError: %s() got an unexpected keyword argument '%s'." \
@@ -364,11 +358,13 @@ def get_params(evaluator, parent_context, func, var_args):
if origin_args not in [f.parent.parent for f in first_values]:
continue
"""
if var_args.get_calling_nodes():
# There might not be a valid calling node so check for that first.
add_argument_issue(parent_context, 'type-error-too-many-arguments', lazy_context, message=m)
return result_params
def _iterate_star_args(evaluator, array, input_node, func=None):
def _iterate_star_args(context, array, input_node, func=None):
try:
iter_ = array.py__iter__
except AttributeError:
@@ -376,13 +372,13 @@ def _iterate_star_args(evaluator, array, input_node, func=None):
# TODO this func should not be needed.
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)
analysis.add(context, 'type-error-star', input_node, message=m)
else:
for lazy_context in iter_():
yield lazy_context
def _star_star_dict(evaluator, array, input_node, func):
def _star_star_dict(context, array, input_node, func):
from jedi.evaluate.instance import CompiledInstance
if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
# For now ignore this case. In the future add proper iterators and just
@@ -394,7 +390,7 @@ def _star_star_dict(evaluator, array, input_node, func):
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)
analysis.add(context, 'type-error-star-star', input_node, message=m)
return {}