1
0
forked from VimPlug/jedi

Starting to improve function calls.

This commit is contained in:
Dave Halter
2016-10-22 21:02:15 +02:00
parent 2e6603cc2e
commit 75b67af000
8 changed files with 104 additions and 146 deletions

View File

@@ -77,6 +77,7 @@ from jedi.evaluate import compiled
from jedi.evaluate import precedence
from jedi.evaluate import param
from jedi.evaluate import helpers
from jedi.evaluate.context import Context
class Evaluator(object):
@@ -110,8 +111,7 @@ class Evaluator(object):
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
def wrap(self, element, parent_context):
if isinstance(element, (er.Wrapper, er.InstanceElement,
er.ModuleContext, er.FunctionExecution, er.Instance, compiled.CompiledObject)) or element is None:
if isinstance(element, Context) or element is None:
# TODO this is so ugly, please refactor.
return element
@@ -328,7 +328,7 @@ class Evaluator(object):
elif element.type == 'eval_input':
types = self._eval_element_not_cached(context, element.children[0])
else:
types = precedence.calculate_children(self, element.children)
types = precedence.calculate_children(self, context, element.children)
debug.dbg('eval_element result %s', types)
return types
@@ -341,13 +341,13 @@ class Evaluator(object):
if isinstance(atom, tree.Name):
# This is the first global lookup.
stmt = atom.get_definition()
if isinstance(context, er.FunctionExecution):
# Adjust scope: If the name is not in the suite, it's a param
# default or annotation and will be resolved as part of the
# parent scope.
colon = scope.children.index(':')
if atom.start_pos < scope.children[colon + 1].start_pos:
scope = scope.get_parent_scope()
#if isinstance(context, er.FunctionExecution):
## Adjust scope: If the name is not in the suite, it's a param
## default or annotation and will be resolved as part of the
## parent scope.
#colon = scope.children.index(':')
#if atom.start_pos < scope.children[colon + 1].start_pos:
##scope = scope.get_parent_scope()
if isinstance(stmt, tree.CompFor):
stmt = stmt.get_parent_until((tree.ClassOrFunc, tree.ExprStmt))
if stmt.type != 'expr_stmt':

View File

@@ -56,6 +56,15 @@ class TreeNameDefinition(ContextName):
return _name_to_types(self.parent_context._evaluator, self.parent_context, self.name, None)
class ParamName(ContextName):
def __init__(self, parent_context, name):
self.parent_context = parent_context
self.name = name
def infer(self):
return set()
class AbstractFilter(object):
_until_position = None
@@ -89,9 +98,9 @@ class AbstractUsedNamesFilter(AbstractFilter):
except KeyError:
return []
return self._convert_to_names(self._filter(names))
return self._convert_names(self._filter(names))
def _convert_to_names(self, names):
def _convert_names(self, names):
return [TreeNameDefinition(self._context, name) for name in names]
def values(self):
@@ -127,7 +136,7 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
class FunctionExecutionFilter(ParserTreeFilter):
def __init__(self, evaluator, context, parser_scope, executed_function, param_by_name,
def __init__(self, evaluator, context, parser_scope, param_by_name,
until_position=None, origin_scope=None):
super(FunctionExecutionFilter, self).__init__(
evaluator,
@@ -136,16 +145,15 @@ class FunctionExecutionFilter(ParserTreeFilter):
until_position,
origin_scope
)
self._executed_function = executed_function
self._param_by_name = param_by_name
def _filter(self, names):
names = super(FunctionExecutionFilter, self)._filter(names)
names = [self._executed_function.name_for_position(name.start_pos) for name in names]
names = [self._param_by_name(str(name)) if search_ancestor(name, 'param') else name
for name in names]
return names
def _convert_names(self, names):
for name in names:
param = search_ancestor(name, 'param')
if param:
#yield self.context._param_by_name(str(name))
yield ParamName(self._context, name)
else:
yield TreeNameDefinition(self._context, name)
class GlobalNameFilter(AbstractUsedNamesFilter):

View File

@@ -295,19 +295,19 @@ class NameFinder(object):
types = set()
# Add isinstance and other if/assert knowledge.
if isinstance(self.name_str, tree.Name):
# Ignore FunctionExecution parents for now.
flow_scope = self.name_str
until = flow_scope.get_parent_until(er.FunctionExecution)
while not isinstance(until, er.FunctionExecution):
flow_scope = flow_scope.get_parent_scope(include_flows=True)
if flow_scope is None:
break
# TODO check if result is in scope -> no evaluation necessary
n = check_flow_information(self._evaluator, flow_scope,
self.name_str, self.position)
if n:
return n
#if isinstance(self.name_str, tree.Name):
## Ignore FunctionExecution parents for now.
#flow_scope = self.name_str
#until = flow_scope.get_parent_until(er.FunctionExecution)
#while not isinstance(until, er.FunctionExecution):
#flow_scope = flow_scope.get_parent_scope(include_flows=True)
#if flow_scope is None:
#break
## TODO check if result is in scope -> no evaluation necessary
#n = check_flow_information(self._evaluator, flow_scope,
#self.name_str, self.position)
#if n:
#return n
for name in names:
new_types = name.infer()

View File

@@ -83,9 +83,22 @@ def follow_param(evaluator, param):
return _evaluate_for_annotation(evaluator, annotation)
def py__annotations__(funcdef):
return_annotation = funcdef.annotation()
if return_annotation:
dct = {'return': return_annotation}
else:
dct = {}
for function_param in funcdef.params:
param_annotation = function_param.annotation()
if param_annotation is not None:
dct[function_param.name.value] = param_annotation
return dct
@memoize_default(None, evaluator_is_first_arg=True)
def find_return_types(evaluator, func):
annotation = func.py__annotations__().get("return", None)
annotation = py__annotations__(func).get("return", None)
return _evaluate_for_annotation(evaluator, annotation)

View File

@@ -30,19 +30,19 @@ def literals_to_types(evaluator, result):
if is_literal(typ):
# Literals are only valid as long as the operations are
# correct. Otherwise add a value-free instance.
cls = builtin_from_name(evaluator, typ.name.value)
cls = builtin_from_name(evaluator, typ.name.string_name)
new_result |= evaluator.execute(cls)
else:
new_result.add(typ)
return new_result
def calculate_children(evaluator, children):
def calculate_children(evaluator, context, children):
"""
Calculate a list of children with operators.
"""
iterator = iter(children)
types = evaluator.eval_element(next(iterator))
types = context.eval_node(next(iterator))
for operator in iterator:
right = next(iterator)
if tree.is_node(operator, 'comp_op'): # not in / is not
@@ -53,14 +53,14 @@ def calculate_children(evaluator, children):
left_bools = set([left.py__bool__() for left in types])
if left_bools == set([True]):
if operator == 'and':
types = evaluator.eval_element(right)
types = context.eval_node(right)
elif left_bools == set([False]):
if operator != 'and':
types = evaluator.eval_element(right)
types = context.eval_node(right)
# Otherwise continue, because of uncertainty.
else:
types = calculate(evaluator, types, operator,
evaluator.eval_element(right))
context.eval_node(right))
debug.dbg('calculate_children types %s', types)
return types

View File

@@ -92,6 +92,7 @@ class _RecursionNode(object):
def execution_recursion_decorator(func):
return func # TODO remove
def run(execution, **kwargs):
detector = execution._evaluator.execution_recursion_detector
if detector.push_execution(execution):
@@ -130,9 +131,6 @@ class ExecutionRecursionDetector(object):
self.recursion_level -= 1
def push_execution(self, execution):
self.execution_funcs.add(execution.base)
self.parent_execution_funcs.append(execution.base)
return True # Remove
in_par_execution_funcs = execution.base in self.parent_execution_funcs
in_execution_funcs = execution.base in self.execution_funcs
self.recursion_level += 1

View File

@@ -57,25 +57,21 @@ from jedi.evaluate import flow_analysis
from jedi.evaluate import imports
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
GlobalNameFilter, DictFilter, ContextName
from jedi.evaluate.context import Context
from jedi.evaluate.context import TreeContext
class Executed(Context):
class Executed(TreeContext):
"""
An instance is also an executable - because __init__ is called
:param var_args: The param input array, consist of a parser node or a list.
"""
def __init__(self, evaluator, parent_context, base, var_args):
def __init__(self, evaluator, parent_context, var_args):
super(Executed, self).__init__(evaluator, parent_context=parent_context)
self.base = base
self.var_args = var_args
def is_scope(self):
return True
def get_parent_until(self, *args, **kwargs):
return tree.Base.get_parent_until(self, *args, **kwargs)
class Instance(use_metaclass(CachedMetaClass, Executed)):
"""
@@ -132,7 +128,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)):
func = self.get_subscope_by_name('__init__')
except KeyError:
return None
return FunctionExecution(self._evaluator, self, func, self.var_args)
return FunctionExecutionContext(self._evaluator, self, func, self.var_args)
def _get_func_self_name(self, func):
"""
@@ -257,12 +253,6 @@ class Instance(use_metaclass(CachedMetaClass, Executed)):
def name(self):
return ContextName(self, self.base.name)
def __getattr__(self, name):
if name not in ['start_pos', 'end_pos', 'get_imports', 'type',
'doc', 'raw_doc']:
return super(Instance, self).__getattribute__(name)
return getattr(self.base, name)
def __repr__(self):
dec = ''
if self.decorates is not None:
@@ -380,7 +370,7 @@ def get_instance_el(evaluator, instance, var, is_class_var=False):
return InstanceName(var, parent)
elif var.type != 'funcdef' \
and isinstance(var, (Instance, compiled.CompiledObject, tree.Leaf,
tree.Module, FunctionExecution)):
tree.Module, FunctionExecutionContext)):
return var
var = evaluator.wrap(var)
@@ -451,9 +441,6 @@ class InstanceElement(use_metaclass(CachedMetaClass, tree.Base)):
return get_instance_el(self._evaluator, self.instance, self.var[index],
self.is_class_var)
def __getattr__(self, name):
return getattr(self.var, name)
def isinstance(self, *cls):
return isinstance(self.var, cls)
@@ -496,7 +483,7 @@ class Wrapper(tree.Base):
return ContextName(self, name)
class ClassContext(use_metaclass(CachedMetaClass, Context, Wrapper)):
class ClassContext(use_metaclass(CachedMetaClass, TreeContext, Wrapper)):
"""
This class is not only important to extend `tree.Class`, it is also a
important for descriptors (if the descriptor methods are evaluated or not).
@@ -602,7 +589,7 @@ class ClassContext(use_metaclass(CachedMetaClass, Context, Wrapper)):
return "<e%s of %s>" % (type(self).__name__, self.base)
class Function(use_metaclass(CachedMetaClass, Context, Wrapper)):
class Function(use_metaclass(CachedMetaClass, TreeContext, Wrapper)):
"""
Needed because of decorators. Decorators are evaluated here.
"""
@@ -690,21 +677,12 @@ class Function(use_metaclass(CachedMetaClass, Context, Wrapper)):
if self.base.is_generator():
return set([iterable.Generator(self._evaluator, self, params)])
else:
return FunctionExecution(self._evaluator, self.parent_context, self, params).get_return_types()
@memoize_default()
def py__annotations__(self):
parser_func = self.base
return_annotation = parser_func.annotation()
if return_annotation:
dct = {'return': return_annotation}
else:
dct = {}
for function_param in parser_func.params:
param_annotation = function_param.annotation()
if param_annotation is not None:
dct[function_param.name.value] = param_annotation
return dct
return FunctionExecutionContext(
self._evaluator,
self.parent_context,
self.base,
params
).get_return_types()
def py__class__(self):
# This differentiation is only necessary for Python2. Python3 does not
@@ -715,9 +693,6 @@ class Function(use_metaclass(CachedMetaClass, Context, Wrapper)):
name = 'FUNCTION_CLASS'
return compiled.get_special_object(self._evaluator, name)
def __getattr__(self, name):
return getattr(self.base_func, name)
def __repr__(self):
dec = ''
if self.decorates is not None:
@@ -730,7 +705,7 @@ class LambdaWrapper(Function):
return self
class FunctionExecution(Executed):
class FunctionExecutionContext(Executed):
"""
This class is used to evaluate functions and their returns.
@@ -741,10 +716,9 @@ class FunctionExecution(Executed):
"""
type = 'funcdef'
def __init__(self, evaluator, parent_context, base, var_args):
super(FunctionExecution, self).__init__(evaluator, parent_context, base, var_args)
self._copy_dict = {}
self._original_function = funcdef = base
def __init__(self, evaluator, parent_context, funcdef, var_args):
super(FunctionExecutionContext, self).__init__(evaluator, parent_context, var_args)
self._funcdef = funcdef
if isinstance(funcdef, mixed.MixedObject):
# The extra information in mixed is not needed anymore. We can just
# unpack it and give it the tree object.
@@ -763,11 +737,11 @@ class FunctionExecution(Executed):
@memoize_default(default=set())
@recursion.execution_recursion_decorator
def get_return_types(self, check_yields=False):
func = self.base
if func.isinstance(LambdaWrapper):
funcdef = self._funcdef
if funcdef.type in ('lambdef', 'lambdef_nocond'):
return self._evaluator.eval_element(self.children[-1])
"""
if func.listeners:
# Feed the listeners, with the params.
for listener in func.listeners:
@@ -776,16 +750,19 @@ class FunctionExecution(Executed):
# execution ongoing. In this case Jedi is interested in the
# inserted params, not in the actual execution of the function.
return set()
"""
if check_yields:
types = set()
returns = self.yields
returns = funcdef.yields
else:
returns = self.returns
types = set(docstrings.find_return_types(self._evaluator, func))
types |= set(pep0484.find_return_types(self._evaluator, func))
returns = funcdef.returns
types = set(docstrings.find_return_types(self._evaluator, funcdef))
types |= set(pep0484.find_return_types(self._evaluator, funcdef))
for r in returns:
types |= self.eval_node(r.children[1])
continue # TODO REMOVE
check = flow_analysis.break_check(self._evaluator, self, r)
if check is flow_analysis.UNREACHABLE:
debug.dbg('Return unreachable: %s', r)
@@ -793,26 +770,26 @@ class FunctionExecution(Executed):
if check_yields:
types |= iterable.unite(self._eval_yield(r))
else:
types |= self._evaluator.eval_element(r.children[1])
types |= self.eval_node(r.children[1])
if check is flow_analysis.REACHABLE:
debug.dbg('Return reachable: %s', r)
break
return types
def _eval_yield(self, yield_expr):
element = yield_expr.children[1]
if element.type == 'yield_arg':
node = yield_expr.children[1]
if node.type == 'yield_arg':
# It must be a yield from.
yield_from_types = self._evaluator.eval_element(element.children[1])
for result in iterable.py__iter__(self._evaluator, yield_from_types, element):
yield_from_types = self.eval_node(node)
for result in iterable.py__iter__(self._evaluator, yield_from_types, node):
yield result
else:
yield self._evaluator.eval_element(element)
yield self.eval_node(node)
@recursion.execution_recursion_decorator
def get_yield_types(self):
yields = self.yields
stopAt = tree.ForStmt, tree.WhileStmt, FunctionExecution, tree.IfStmt
stopAt = tree.ForStmt, tree.WhileStmt, tree.IfStmt
for_parents = [(x, x.get_parent_until((stopAt))) for x in yields]
# Calculate if the yields are placed within the same for loop.
@@ -857,55 +834,20 @@ class FunctionExecution(Executed):
del evaluator.predefined_if_name_dict_dict[for_stmt]
def get_filters(self, search_global, until_position=None, origin_scope=None):
yield FunctionExecutionFilter(self._evaluator, self, self._original_function,
self._copied_funcdef,
yield FunctionExecutionFilter(self._evaluator, self, self._funcdef,
self.param_by_name,
until_position,
origin_scope=origin_scope)
@memoize_default(default=NO_DEFAULT)
def _get_params(self):
"""
This returns the params for an TODO and is injected as a
'hack' into the tree.Function class.
This needs to be here, because Instance can have __init__ functions,
which act the same way as normal functions.
"""
return param.get_params(self._evaluator, self.base, self.var_args)
return param.get_params(self._evaluator, self._funcdef, self.var_args)
def param_by_name(self, name):
return [n for n in self._get_params() if str(n) == name][0]
def name_for_position(self, position):
return tree.Function.name_for_position(self, position)
def __getattr__(self, name):
if name not in ['start_pos', 'end_pos', 'imports', 'name', 'type']:
return super(FunctionExecution, self).__getattribute__(name)
return getattr(self.base, name)
@common.safe_property
@memoize_default()
def returns(self):
return tree.Scope._search_in_scope(self, tree.ReturnStmt)
@common.safe_property
@memoize_default()
def yields(self):
return tree.Scope._search_in_scope(self, tree.YieldExpr)
@common.safe_property
@memoize_default()
def statements(self):
return tree.Scope._search_in_scope(self, tree.ExprStmt)
@common.safe_property
@memoize_default()
def subscopes(self):
return tree.Scope._search_in_scope(self, tree.Scope)
def __repr__(self):
return "<%s of %s>" % (type(self).__name__, self.base)
return "<%s of %s>" % (type(self).__name__, self._funcdef)
class GlobalName(helpers.FakeName):
@@ -919,7 +861,7 @@ class GlobalName(helpers.FakeName):
name.start_pos, is_definition=True)
class ModuleContext(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
class ModuleContext(use_metaclass(CachedMetaClass, TreeContext, Wrapper)):
parent_context = None
def __init__(self, evaluator, module, parent_module=None):
@@ -1095,8 +1037,5 @@ class ModuleContext(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
def py__class__(self):
return compiled.get_special_object(self._evaluator, 'MODULE_CLASS')
def __getattr__(self, name):
return getattr(self._module, name)
def __repr__(self):
return "<%s: %s>" % (type(self).__name__, self._module)

View File

@@ -37,8 +37,8 @@ def execute(evaluator, obj, arguments):
else:
if obj.parent_context == evaluator.BUILTINS:
module_name = 'builtins'
elif isinstance(obj.parent, tree.Module):
module_name = str(obj.parent.name)
elif isinstance(obj.parent_context, er.ModuleContext):
module_name = obj.parent_context.name.string_name
else:
module_name = ''