forked from VimPlug/jedi
Move arguments to a separate module.
This commit is contained in:
@@ -27,7 +27,7 @@ from jedi.api import helpers
|
|||||||
from jedi.api.completion import Completion
|
from jedi.api.completion import Completion
|
||||||
from jedi.evaluate import Evaluator
|
from jedi.evaluate import Evaluator
|
||||||
from jedi.evaluate import imports
|
from jedi.evaluate import imports
|
||||||
from jedi.evaluate.param import try_iter_content
|
from jedi.evaluate.arguments import try_iter_content
|
||||||
from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf
|
from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf
|
||||||
from jedi.evaluate.sys_path import get_venv_path, dotted_path_in_sys_path
|
from jedi.evaluate.sys_path import get_venv_path, dotted_path_in_sys_path
|
||||||
from jedi.evaluate.filters import TreeNameDefinition
|
from jedi.evaluate.filters import TreeNameDefinition
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ def _check_for_exception_catch(node_context, jedi_name, exception, payload=None)
|
|||||||
assert trailer.type == 'trailer'
|
assert trailer.type == 'trailer'
|
||||||
arglist = trailer.children[1]
|
arglist = trailer.children[1]
|
||||||
assert arglist.type == 'arglist'
|
assert arglist.type == 'arglist'
|
||||||
from jedi.evaluate.param import TreeArguments
|
from jedi.evaluate.arguments import TreeArguments
|
||||||
args = list(TreeArguments(node_context.evaluator, node_context, arglist).unpack())
|
args = list(TreeArguments(node_context.evaluator, node_context, arglist).unpack())
|
||||||
# Arguments should be very simple
|
# Arguments should be very simple
|
||||||
assert len(args) == 2
|
assert len(args) == 2
|
||||||
|
|||||||
247
jedi/evaluate/arguments.py
Normal file
247
jedi/evaluate/arguments.py
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
from parso.python import tree
|
||||||
|
|
||||||
|
from jedi._compatibility import zip_longest
|
||||||
|
from jedi import debug
|
||||||
|
from jedi.evaluate import analysis
|
||||||
|
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
|
||||||
|
LazyTreeContext, get_merged_lazy_context
|
||||||
|
from jedi.evaluate.filters import ParamName
|
||||||
|
from jedi.evaluate.base_context import NO_CONTEXTS
|
||||||
|
from jedi.evaluate.context import iterable
|
||||||
|
from jedi.evaluate.param import get_params, ExecutedParam
|
||||||
|
|
||||||
|
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 lazy_context in f():
|
||||||
|
try_iter_content(lazy_context.infer(), depth + 1)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractArguments(object):
|
||||||
|
context = None
|
||||||
|
|
||||||
|
def eval_argument_clinic(self, parameters):
|
||||||
|
"""Uses a list with argument clinic information (see PEP 436)."""
|
||||||
|
iterator = self.unpack()
|
||||||
|
for i, (name, optional, allow_kwargs) in enumerate(parameters):
|
||||||
|
key, argument = next(iterator, (None, None))
|
||||||
|
if key is not None:
|
||||||
|
raise NotImplementedError
|
||||||
|
if argument is None and not optional:
|
||||||
|
debug.warning('TypeError: %s expected at least %s arguments, got %s',
|
||||||
|
name, len(parameters), i)
|
||||||
|
raise ValueError
|
||||||
|
values = NO_CONTEXTS if argument is None else argument.infer()
|
||||||
|
|
||||||
|
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 eval_all(self, funcdef=None):
|
||||||
|
"""
|
||||||
|
Evaluates all arguments as a support for static analysis
|
||||||
|
(normally Jedi).
|
||||||
|
"""
|
||||||
|
for key, lazy_context in self.unpack():
|
||||||
|
types = lazy_context.infer()
|
||||||
|
try_iter_content(types)
|
||||||
|
|
||||||
|
def get_calling_nodes(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def unpack(self, funcdef=None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_params(self, execution_context):
|
||||||
|
return get_params(execution_context, self)
|
||||||
|
|
||||||
|
|
||||||
|
class AnonymousArguments(AbstractArguments):
|
||||||
|
def get_params(self, execution_context):
|
||||||
|
from jedi.evaluate.dynamic import search_params
|
||||||
|
return search_params(
|
||||||
|
execution_context.evaluator,
|
||||||
|
execution_context,
|
||||||
|
execution_context.tree_node
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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 (self.argument_node.type == 'arglist' or (
|
||||||
|
# in python 3.5 **arg is an argument, not arglist
|
||||||
|
(self.argument_node.type == '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 child.type == '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, funcdef=None):
|
||||||
|
named_args = []
|
||||||
|
for star_count, el in self._split():
|
||||||
|
if star_count == 1:
|
||||||
|
arrays = self.context.eval_node(el)
|
||||||
|
iterators = [_iterate_star_args(self.context, a, el, funcdef)
|
||||||
|
for a in arrays]
|
||||||
|
iterators = list(iterators)
|
||||||
|
for values in list(zip_longest(*iterators)):
|
||||||
|
# TODO zip_longest yields None, that means this would raise
|
||||||
|
# an exception?
|
||||||
|
yield None, get_merged_lazy_context(
|
||||||
|
[v for v in values if v is not None]
|
||||||
|
)
|
||||||
|
elif star_count == 2:
|
||||||
|
arrays = self._evaluator.eval_element(self.context, el)
|
||||||
|
for dct in arrays:
|
||||||
|
for key, values in _star_star_dict(self.context, dct, el, funcdef):
|
||||||
|
yield key, values
|
||||||
|
else:
|
||||||
|
if el.type == 'argument':
|
||||||
|
c = el.children
|
||||||
|
if len(c) == 3: # Keyword argument.
|
||||||
|
named_args.append((c[0].value, LazyTreeContext(self.context, c[2]),))
|
||||||
|
else: # Generator comprehension.
|
||||||
|
# Include the brackets with the parent.
|
||||||
|
comp = iterable.GeneratorComprehension(
|
||||||
|
self._evaluator, self.context, self.argument_node.parent)
|
||||||
|
yield None, LazyKnownContext(comp)
|
||||||
|
else:
|
||||||
|
yield None, LazyTreeContext(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_tree_tuple_objects(self):
|
||||||
|
for star_count, argument in self._split():
|
||||||
|
if argument.type == 'argument':
|
||||||
|
argument, default = argument.children[::2]
|
||||||
|
else:
|
||||||
|
default = None
|
||||||
|
yield argument, default, star_count
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
|
||||||
|
|
||||||
|
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, star_count in reversed(list(arguments.as_tree_tuple_objects())):
|
||||||
|
if not star_count 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
|
||||||
|
break
|
||||||
|
|
||||||
|
return [arguments.argument_node or arguments.trailer]
|
||||||
|
|
||||||
|
|
||||||
|
class ValuesArguments(AbstractArguments):
|
||||||
|
def __init__(self, values_list):
|
||||||
|
self._values_list = values_list
|
||||||
|
|
||||||
|
def unpack(self, funcdef=None):
|
||||||
|
for values in self._values_list:
|
||||||
|
yield None, LazyKnownContexts(values)
|
||||||
|
|
||||||
|
def get_calling_nodes(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self._values_list)
|
||||||
|
|
||||||
|
|
||||||
|
def _iterate_star_args(context, array, input_node, funcdef=None):
|
||||||
|
try:
|
||||||
|
iter_ = array.py__iter__
|
||||||
|
except AttributeError:
|
||||||
|
if funcdef is not None:
|
||||||
|
# TODO this funcdef should not be needed.
|
||||||
|
m = "TypeError: %s() argument after * must be a sequence, not %s" \
|
||||||
|
% (funcdef.name.value, array)
|
||||||
|
analysis.add(context, 'type-error-star', input_node, message=m)
|
||||||
|
else:
|
||||||
|
for lazy_context in iter_():
|
||||||
|
yield lazy_context
|
||||||
|
|
||||||
|
|
||||||
|
def _star_star_dict(context, array, input_node, funcdef):
|
||||||
|
from jedi.evaluate.context.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
|
||||||
|
# make one call without crazy isinstance checks.
|
||||||
|
return {}
|
||||||
|
elif isinstance(array, iterable.AbstractIterable) and array.array_type == 'dict':
|
||||||
|
return array.exact_key_items()
|
||||||
|
else:
|
||||||
|
if funcdef is not None:
|
||||||
|
m = "TypeError: %s argument after ** must be a mapping, not %s" \
|
||||||
|
% (funcdef.name.value, array)
|
||||||
|
analysis.add(context, 'type-error-star-star', input_node, message=m)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class Context(BaseContext):
|
|||||||
"""
|
"""
|
||||||
Execute a function with already executed arguments.
|
Execute a function with already executed arguments.
|
||||||
"""
|
"""
|
||||||
from jedi.evaluate.param import ValuesArguments
|
from jedi.evaluate.arguments import ValuesArguments
|
||||||
arguments = ValuesArguments([ContextSet(value) for value in value_list])
|
arguments = ValuesArguments([ContextSet(value) for value in value_list])
|
||||||
return self.execute(arguments)
|
return self.execute(arguments)
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ from jedi.evaluate import compiled
|
|||||||
from jedi.evaluate import recursion
|
from jedi.evaluate import recursion
|
||||||
from jedi.evaluate import docstrings
|
from jedi.evaluate import docstrings
|
||||||
from jedi.evaluate import pep0484
|
from jedi.evaluate import pep0484
|
||||||
from jedi.evaluate import param
|
|
||||||
from jedi.evaluate import flow_analysis
|
from jedi.evaluate import flow_analysis
|
||||||
from jedi.evaluate import helpers
|
from jedi.evaluate import helpers
|
||||||
|
from jedi.evaluate.arguments import AnonymousArguments
|
||||||
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
|
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
|
||||||
ContextName, AbstractNameDefinition, ParamName
|
ContextName, AbstractNameDefinition, ParamName
|
||||||
from jedi.evaluate.base_context import ContextualizedNode, NO_CONTEXTS, \
|
from jedi.evaluate.base_context import ContextualizedNode, NO_CONTEXTS, \
|
||||||
@@ -71,7 +71,7 @@ class FunctionContext(use_metaclass(CachedMetaClass, TreeContext)):
|
|||||||
|
|
||||||
def get_function_execution(self, arguments=None):
|
def get_function_execution(self, arguments=None):
|
||||||
if arguments is None:
|
if arguments is None:
|
||||||
arguments = param.AnonymousArguments()
|
arguments = AnonymousArguments()
|
||||||
|
|
||||||
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
|
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from jedi.evaluate.base_context import Context, NO_CONTEXTS, ContextSet, \
|
|||||||
iterator_to_context_set
|
iterator_to_context_set
|
||||||
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
|
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
|
||||||
from jedi.evaluate.cache import evaluator_method_cache
|
from jedi.evaluate.cache import evaluator_method_cache
|
||||||
from jedi.evaluate.param import AbstractArguments, AnonymousArguments
|
from jedi.evaluate.arguments import AbstractArguments, AnonymousArguments
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.evaluate.context.function import FunctionExecutionContext, FunctionContext
|
from jedi.evaluate.context.function import FunctionExecutionContext, FunctionContext
|
||||||
from jedi.evaluate.context.klass import ClassContext, apply_py__get__
|
from jedi.evaluate.context.klass import ClassContext, apply_py__get__
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ def _check_array_additions(context, sequence):
|
|||||||
>>> a = [""]
|
>>> a = [""]
|
||||||
>>> a.append(1)
|
>>> a.append(1)
|
||||||
"""
|
"""
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate import arguments
|
||||||
|
|
||||||
debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
|
debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
|
||||||
module_context = context.get_root_context()
|
module_context = context.get_root_context()
|
||||||
@@ -543,7 +543,7 @@ def _check_array_additions(context, sequence):
|
|||||||
return ContextSet()
|
return ContextSet()
|
||||||
|
|
||||||
def find_additions(context, arglist, add_name):
|
def find_additions(context, arglist, add_name):
|
||||||
params = list(param.TreeArguments(context.evaluator, context, arglist).unpack())
|
params = list(arguments.TreeArguments(context.evaluator, context, arglist).unpack())
|
||||||
result = set()
|
result = set()
|
||||||
if add_name in ['insert']:
|
if add_name in ['insert']:
|
||||||
params = params[1:]
|
params = params[1:]
|
||||||
@@ -614,8 +614,8 @@ def get_dynamic_array_instance(instance):
|
|||||||
return instance.var_args
|
return instance.var_args
|
||||||
|
|
||||||
ai = _ArrayInstance(instance)
|
ai = _ArrayInstance(instance)
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate import arguments
|
||||||
return param.ValuesArguments([ContextSet(ai)])
|
return arguments.ValuesArguments([ContextSet(ai)])
|
||||||
|
|
||||||
|
|
||||||
class _ArrayInstance(object):
|
class _ArrayInstance(object):
|
||||||
@@ -643,8 +643,8 @@ class _ArrayInstance(object):
|
|||||||
for lazy in lazy_context.infer().iterate():
|
for lazy in lazy_context.infer().iterate():
|
||||||
yield lazy
|
yield lazy
|
||||||
|
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate import arguments
|
||||||
if isinstance(var_args, param.TreeArguments):
|
if isinstance(var_args, arguments.TreeArguments):
|
||||||
additions = _check_array_additions(var_args.context, self.instance)
|
additions = _check_array_additions(var_args.context, self.instance)
|
||||||
for addition in additions:
|
for addition in additions:
|
||||||
yield addition
|
yield addition
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)):
|
|||||||
def py__bases__(self):
|
def py__bases__(self):
|
||||||
arglist = self.tree_node.get_super_arglist()
|
arglist = self.tree_node.get_super_arglist()
|
||||||
if arglist:
|
if arglist:
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate import arguments
|
||||||
args = param.TreeArguments(self.evaluator, self, arglist)
|
args = arguments.TreeArguments(self.evaluator, self, arglist)
|
||||||
return [value for key, value in args.unpack() if key is None]
|
return [value for key, value in args.unpack() if key is None]
|
||||||
else:
|
else:
|
||||||
return [LazyKnownContext(compiled.create(self.evaluator, object))]
|
return [LazyKnownContext(compiled.create(self.evaluator, object))]
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ from jedi import settings
|
|||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.evaluate.cache import evaluator_function_cache
|
from jedi.evaluate.cache import evaluator_function_cache
|
||||||
from jedi.evaluate import imports
|
from jedi.evaluate import imports
|
||||||
from jedi.evaluate.param import TreeArguments, create_default_params
|
from jedi.evaluate.arguments import TreeArguments
|
||||||
|
from jedi.evaluate.param import create_default_params
|
||||||
from jedi.evaluate.helpers import is_stdlib_path
|
from jedi.evaluate.helpers import is_stdlib_path
|
||||||
from jedi.evaluate.utils import to_list
|
from jedi.evaluate.utils import to_list
|
||||||
from jedi.parser_utils import get_parent_scope
|
from jedi.parser_utils import get_parent_scope
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from jedi.evaluate.context import AbstractInstanceContext
|
|||||||
from jedi.evaluate import compiled
|
from jedi.evaluate import compiled
|
||||||
from jedi.evaluate import analysis
|
from jedi.evaluate import analysis
|
||||||
from jedi.evaluate import flow_analysis
|
from jedi.evaluate import flow_analysis
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate.arguments import TreeArguments
|
||||||
from jedi.evaluate import helpers
|
from jedi.evaluate import helpers
|
||||||
from jedi.evaluate.context import iterable
|
from jedi.evaluate.context import iterable
|
||||||
from jedi.evaluate.filters import get_global_filters, TreeNameDefinition
|
from jedi.evaluate.filters import get_global_filters, TreeNameDefinition
|
||||||
@@ -231,7 +231,7 @@ def _check_isinstance_type(context, element, search_name):
|
|||||||
|
|
||||||
# arglist stuff
|
# arglist stuff
|
||||||
arglist = trailer.children[1]
|
arglist = trailer.children[1]
|
||||||
args = param.TreeArguments(context.evaluator, context, arglist, trailer)
|
args = TreeArguments(context.evaluator, context, arglist, trailer)
|
||||||
param_list = list(args.unpack())
|
param_list = list(args.unpack())
|
||||||
# Disallow keyword arguments
|
# Disallow keyword arguments
|
||||||
assert len(param_list) == 2
|
assert len(param_list) == 2
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from jedi._compatibility import zip_longest
|
|
||||||
from jedi import debug
|
|
||||||
from jedi.evaluate.utils import PushBackIterator
|
from jedi.evaluate.utils import PushBackIterator
|
||||||
from parso.python import tree
|
|
||||||
from jedi.evaluate import analysis
|
from jedi.evaluate import analysis
|
||||||
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
|
from jedi.evaluate.lazy_context import LazyKnownContext, \
|
||||||
LazyTreeContext, LazyUnknownContext, get_merged_lazy_context
|
LazyTreeContext, LazyUnknownContext
|
||||||
from jedi.evaluate import docstrings
|
from jedi.evaluate import docstrings
|
||||||
from jedi.evaluate import pep0484
|
from jedi.evaluate import pep0484
|
||||||
from jedi.evaluate.filters import ParamName
|
|
||||||
from jedi.evaluate.base_context import NO_CONTEXTS
|
|
||||||
from jedi.evaluate.context import iterable
|
from jedi.evaluate.context import iterable
|
||||||
|
|
||||||
|
|
||||||
def add_argument_issue(parent_context, error_name, lazy_context, message):
|
def _add_argument_issue(parent_context, error_name, lazy_context, message):
|
||||||
if isinstance(lazy_context, LazyTreeContext):
|
if isinstance(lazy_context, LazyTreeContext):
|
||||||
node = lazy_context.data
|
node = lazy_context.data
|
||||||
if node.parent.type == 'argument':
|
if node.parent.type == 'argument':
|
||||||
@@ -22,211 +17,6 @@ def add_argument_issue(parent_context, error_name, lazy_context, message):
|
|||||||
analysis.add(parent_context, error_name, node, message)
|
analysis.add(parent_context, error_name, node, message)
|
||||||
|
|
||||||
|
|
||||||
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 lazy_context in f():
|
|
||||||
try_iter_content(lazy_context.infer(), depth + 1)
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractArguments():
|
|
||||||
context = None
|
|
||||||
|
|
||||||
def eval_argument_clinic(self, parameters):
|
|
||||||
"""Uses a list with argument clinic information (see PEP 436)."""
|
|
||||||
iterator = self.unpack()
|
|
||||||
for i, (name, optional, allow_kwargs) in enumerate(parameters):
|
|
||||||
key, argument = next(iterator, (None, None))
|
|
||||||
if key is not None:
|
|
||||||
raise NotImplementedError
|
|
||||||
if argument is None and not optional:
|
|
||||||
debug.warning('TypeError: %s expected at least %s arguments, got %s',
|
|
||||||
name, len(parameters), i)
|
|
||||||
raise ValueError
|
|
||||||
values = NO_CONTEXTS if argument is None else argument.infer()
|
|
||||||
|
|
||||||
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 eval_all(self, funcdef=None):
|
|
||||||
"""
|
|
||||||
Evaluates all arguments as a support for static analysis
|
|
||||||
(normally Jedi).
|
|
||||||
"""
|
|
||||||
for key, lazy_context in self.unpack():
|
|
||||||
types = lazy_context.infer()
|
|
||||||
try_iter_content(types)
|
|
||||||
|
|
||||||
def get_calling_nodes(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def unpack(self, funcdef=None):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_params(self, execution_context):
|
|
||||||
return get_params(execution_context, self)
|
|
||||||
|
|
||||||
|
|
||||||
class AnonymousArguments(AbstractArguments):
|
|
||||||
def get_params(self, execution_context):
|
|
||||||
from jedi.evaluate.dynamic import search_params
|
|
||||||
return search_params(
|
|
||||||
execution_context.evaluator,
|
|
||||||
execution_context,
|
|
||||||
execution_context.tree_node
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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 (self.argument_node.type == 'arglist' or (
|
|
||||||
# in python 3.5 **arg is an argument, not arglist
|
|
||||||
(self.argument_node.type == '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 child.type == '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, funcdef=None):
|
|
||||||
named_args = []
|
|
||||||
for star_count, el in self._split():
|
|
||||||
if star_count == 1:
|
|
||||||
arrays = self.context.eval_node(el)
|
|
||||||
iterators = [_iterate_star_args(self.context, a, el, funcdef)
|
|
||||||
for a in arrays]
|
|
||||||
iterators = list(iterators)
|
|
||||||
for values in list(zip_longest(*iterators)):
|
|
||||||
# TODO zip_longest yields None, that means this would raise
|
|
||||||
# an exception?
|
|
||||||
yield None, get_merged_lazy_context(
|
|
||||||
[v for v in values if v is not None]
|
|
||||||
)
|
|
||||||
elif star_count == 2:
|
|
||||||
arrays = self._evaluator.eval_element(self.context, el)
|
|
||||||
for dct in arrays:
|
|
||||||
for key, values in _star_star_dict(self.context, dct, el, funcdef):
|
|
||||||
yield key, values
|
|
||||||
else:
|
|
||||||
if el.type == 'argument':
|
|
||||||
c = el.children
|
|
||||||
if len(c) == 3: # Keyword argument.
|
|
||||||
named_args.append((c[0].value, LazyTreeContext(self.context, c[2]),))
|
|
||||||
else: # Generator comprehension.
|
|
||||||
# Include the brackets with the parent.
|
|
||||||
comp = iterable.GeneratorComprehension(
|
|
||||||
self._evaluator, self.context, self.argument_node.parent)
|
|
||||||
yield None, LazyKnownContext(comp)
|
|
||||||
else:
|
|
||||||
yield None, LazyTreeContext(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_tree_tuple_objects(self):
|
|
||||||
for star_count, argument in self._split():
|
|
||||||
if argument.type == 'argument':
|
|
||||||
argument, default = argument.children[::2]
|
|
||||||
else:
|
|
||||||
default = None
|
|
||||||
yield argument, default, star_count
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
|
|
||||||
|
|
||||||
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, star_count in reversed(list(arguments.as_tree_tuple_objects())):
|
|
||||||
if not star_count 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
|
|
||||||
break
|
|
||||||
|
|
||||||
return [arguments.argument_node or arguments.trailer]
|
|
||||||
|
|
||||||
|
|
||||||
class ValuesArguments(AbstractArguments):
|
|
||||||
def __init__(self, values_list):
|
|
||||||
self._values_list = values_list
|
|
||||||
|
|
||||||
def unpack(self, funcdef=None):
|
|
||||||
for values in self._values_list:
|
|
||||||
yield None, LazyKnownContexts(values)
|
|
||||||
|
|
||||||
def get_calling_nodes(self):
|
|
||||||
return []
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self._values_list)
|
|
||||||
|
|
||||||
|
|
||||||
class ExecutedParam(object):
|
class ExecutedParam(object):
|
||||||
"""Fake a param and give it values."""
|
"""Fake a param and give it values."""
|
||||||
def __init__(self, execution_context, param_node, lazy_context):
|
def __init__(self, execution_context, param_node, lazy_context):
|
||||||
@@ -352,7 +142,7 @@ def get_params(execution_context, var_args):
|
|||||||
for key, lazy_context in non_matching_keys.items():
|
for key, lazy_context in non_matching_keys.items():
|
||||||
m = "TypeError: %s() got an unexpected keyword argument '%s'." \
|
m = "TypeError: %s() got an unexpected keyword argument '%s'." \
|
||||||
% (funcdef.name, key)
|
% (funcdef.name, key)
|
||||||
add_argument_issue(
|
_add_argument_issue(
|
||||||
parent_context,
|
parent_context,
|
||||||
'type-error-keyword-argument',
|
'type-error-keyword-argument',
|
||||||
lazy_context,
|
lazy_context,
|
||||||
@@ -367,40 +157,10 @@ def get_params(execution_context, var_args):
|
|||||||
first_key, lazy_context = remaining_arguments[0]
|
first_key, lazy_context = remaining_arguments[0]
|
||||||
if var_args.get_calling_nodes():
|
if var_args.get_calling_nodes():
|
||||||
# There might not be a valid calling node so check for that first.
|
# 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)
|
_add_argument_issue(parent_context, 'type-error-too-many-arguments', lazy_context, message=m)
|
||||||
return result_params
|
return result_params
|
||||||
|
|
||||||
|
|
||||||
def _iterate_star_args(context, array, input_node, funcdef=None):
|
|
||||||
try:
|
|
||||||
iter_ = array.py__iter__
|
|
||||||
except AttributeError:
|
|
||||||
if funcdef is not None:
|
|
||||||
# TODO this funcdef should not be needed.
|
|
||||||
m = "TypeError: %s() argument after * must be a sequence, not %s" \
|
|
||||||
% (funcdef.name.value, array)
|
|
||||||
analysis.add(context, 'type-error-star', input_node, message=m)
|
|
||||||
else:
|
|
||||||
for lazy_context in iter_():
|
|
||||||
yield lazy_context
|
|
||||||
|
|
||||||
|
|
||||||
def _star_star_dict(context, array, input_node, funcdef):
|
|
||||||
from jedi.evaluate.context.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
|
|
||||||
# make one call without crazy isinstance checks.
|
|
||||||
return {}
|
|
||||||
elif isinstance(array, iterable.AbstractIterable) and array.array_type == 'dict':
|
|
||||||
return array.exact_key_items()
|
|
||||||
else:
|
|
||||||
if funcdef is not None:
|
|
||||||
m = "TypeError: %s argument after ** must be a mapping, not %s" \
|
|
||||||
% (funcdef.name.value, array)
|
|
||||||
analysis.add(context, 'type-error-star-star', input_node, message=m)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def _error_argument_count(funcdef, actual_count):
|
def _error_argument_count(funcdef, actual_count):
|
||||||
params = funcdef.get_params()
|
params = funcdef.get_params()
|
||||||
default_arguments = sum(1 for p in params if p.default or p.star_count)
|
default_arguments = sum(1 for p in params if p.default or p.star_count)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import collections
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate.arguments import ValuesArguments
|
||||||
from jedi.evaluate import analysis
|
from jedi.evaluate import analysis
|
||||||
from jedi.evaluate import compiled
|
from jedi.evaluate import compiled
|
||||||
from jedi.evaluate.context.instance import InstanceFunctionExecution, \
|
from jedi.evaluate.context.instance import InstanceFunctionExecution, \
|
||||||
@@ -208,7 +208,7 @@ def builtins_reversed(evaluator, sequences, obj, arguments):
|
|||||||
# would fail in certain cases like `reversed(x).__iter__` if we
|
# would fail in certain cases like `reversed(x).__iter__` if we
|
||||||
# just returned the result directly.
|
# just returned the result directly.
|
||||||
seq = iterable.FakeSequence(evaluator, 'list', rev)
|
seq = iterable.FakeSequence(evaluator, 'list', rev)
|
||||||
arguments = param.ValuesArguments([ContextSet(seq)])
|
arguments = ValuesArguments([ContextSet(seq)])
|
||||||
return ContextSet(CompiledInstance(evaluator, evaluator.BUILTINS, obj, arguments))
|
return ContextSet(CompiledInstance(evaluator, evaluator.BUILTINS, obj, arguments))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from jedi.evaluate import recursion
|
|||||||
from jedi.evaluate import helpers
|
from jedi.evaluate import helpers
|
||||||
from jedi.evaluate import analysis
|
from jedi.evaluate import analysis
|
||||||
from jedi.evaluate import imports
|
from jedi.evaluate import imports
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate import arguments
|
||||||
from jedi.evaluate.context import ClassContext, FunctionContext
|
from jedi.evaluate.context import ClassContext, FunctionContext
|
||||||
from jedi.evaluate.context import iterable
|
from jedi.evaluate.context import iterable
|
||||||
from jedi.evaluate.context import TreeInstance, CompiledInstance
|
from jedi.evaluate.context import TreeInstance, CompiledInstance
|
||||||
@@ -149,8 +149,8 @@ def eval_trailer(context, base_contexts, trailer):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert trailer_op == '('
|
assert trailer_op == '('
|
||||||
arguments = param.TreeArguments(context.evaluator, context, node, trailer)
|
args = arguments.TreeArguments(context.evaluator, context, node, trailer)
|
||||||
return base_contexts.execute(arguments)
|
return base_contexts.execute(args)
|
||||||
|
|
||||||
|
|
||||||
def eval_atom(context, atom):
|
def eval_atom(context, atom):
|
||||||
@@ -528,7 +528,7 @@ def _apply_decorators(context, node):
|
|||||||
debug.warning('decorator not found: %s on %s', dec, node)
|
debug.warning('decorator not found: %s on %s', dec, node)
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
values = dec_values.execute(param.ValuesArguments([values]))
|
values = dec_values.execute(arguments.ValuesArguments([values]))
|
||||||
if not len(values):
|
if not len(values):
|
||||||
debug.warning('not possible to resolve wrappers found %s', node)
|
debug.warning('not possible to resolve wrappers found %s', node)
|
||||||
return initial
|
return initial
|
||||||
|
|||||||
Reference in New Issue
Block a user