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 precedence
from jedi.evaluate import param from jedi.evaluate import param
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate.context import Context
class Evaluator(object): class Evaluator(object):
@@ -110,8 +111,7 @@ class Evaluator(object):
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
def wrap(self, element, parent_context): def wrap(self, element, parent_context):
if isinstance(element, (er.Wrapper, er.InstanceElement, if isinstance(element, Context) or element is None:
er.ModuleContext, er.FunctionExecution, er.Instance, compiled.CompiledObject)) or element is None:
# TODO this is so ugly, please refactor. # TODO this is so ugly, please refactor.
return element return element
@@ -328,7 +328,7 @@ class Evaluator(object):
elif element.type == 'eval_input': elif element.type == 'eval_input':
types = self._eval_element_not_cached(context, element.children[0]) types = self._eval_element_not_cached(context, element.children[0])
else: else:
types = precedence.calculate_children(self, element.children) types = precedence.calculate_children(self, context, element.children)
debug.dbg('eval_element result %s', types) debug.dbg('eval_element result %s', types)
return types return types
@@ -341,13 +341,13 @@ class Evaluator(object):
if isinstance(atom, tree.Name): if isinstance(atom, tree.Name):
# This is the first global lookup. # This is the first global lookup.
stmt = atom.get_definition() stmt = atom.get_definition()
if isinstance(context, er.FunctionExecution): #if isinstance(context, er.FunctionExecution):
# Adjust scope: If the name is not in the suite, it's a param ## 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 ## default or annotation and will be resolved as part of the
# parent scope. ## parent scope.
colon = scope.children.index(':') #colon = scope.children.index(':')
if atom.start_pos < scope.children[colon + 1].start_pos: #if atom.start_pos < scope.children[colon + 1].start_pos:
scope = scope.get_parent_scope() ##scope = scope.get_parent_scope()
if isinstance(stmt, tree.CompFor): if isinstance(stmt, tree.CompFor):
stmt = stmt.get_parent_until((tree.ClassOrFunc, tree.ExprStmt)) stmt = stmt.get_parent_until((tree.ClassOrFunc, tree.ExprStmt))
if stmt.type != 'expr_stmt': 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) 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): class AbstractFilter(object):
_until_position = None _until_position = None
@@ -89,9 +98,9 @@ class AbstractUsedNamesFilter(AbstractFilter):
except KeyError: except KeyError:
return [] 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] return [TreeNameDefinition(self._context, name) for name in names]
def values(self): def values(self):
@@ -127,7 +136,7 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
class FunctionExecutionFilter(ParserTreeFilter): 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): until_position=None, origin_scope=None):
super(FunctionExecutionFilter, self).__init__( super(FunctionExecutionFilter, self).__init__(
evaluator, evaluator,
@@ -136,16 +145,15 @@ class FunctionExecutionFilter(ParserTreeFilter):
until_position, until_position,
origin_scope origin_scope
) )
self._executed_function = executed_function
self._param_by_name = param_by_name
def _filter(self, names): def _convert_names(self, names):
names = super(FunctionExecutionFilter, self)._filter(names) for name in names:
param = search_ancestor(name, 'param')
names = [self._executed_function.name_for_position(name.start_pos) for name in names] if param:
names = [self._param_by_name(str(name)) if search_ancestor(name, 'param') else name #yield self.context._param_by_name(str(name))
for name in names] yield ParamName(self._context, name)
return names else:
yield TreeNameDefinition(self._context, name)
class GlobalNameFilter(AbstractUsedNamesFilter): class GlobalNameFilter(AbstractUsedNamesFilter):

View File

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

View File

@@ -83,9 +83,22 @@ def follow_param(evaluator, param):
return _evaluate_for_annotation(evaluator, annotation) 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) @memoize_default(None, evaluator_is_first_arg=True)
def find_return_types(evaluator, func): 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) return _evaluate_for_annotation(evaluator, annotation)

View File

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

View File

@@ -92,6 +92,7 @@ class _RecursionNode(object):
def execution_recursion_decorator(func): def execution_recursion_decorator(func):
return func # TODO remove
def run(execution, **kwargs): def run(execution, **kwargs):
detector = execution._evaluator.execution_recursion_detector detector = execution._evaluator.execution_recursion_detector
if detector.push_execution(execution): if detector.push_execution(execution):
@@ -130,9 +131,6 @@ class ExecutionRecursionDetector(object):
self.recursion_level -= 1 self.recursion_level -= 1
def push_execution(self, execution): 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_par_execution_funcs = execution.base in self.parent_execution_funcs
in_execution_funcs = execution.base in self.execution_funcs in_execution_funcs = execution.base in self.execution_funcs
self.recursion_level += 1 self.recursion_level += 1

View File

@@ -57,25 +57,21 @@ from jedi.evaluate import flow_analysis
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \ from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
GlobalNameFilter, DictFilter, ContextName 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 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. :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) super(Executed, self).__init__(evaluator, parent_context=parent_context)
self.base = base
self.var_args = var_args self.var_args = var_args
def is_scope(self): def is_scope(self):
return True return True
def get_parent_until(self, *args, **kwargs):
return tree.Base.get_parent_until(self, *args, **kwargs)
class Instance(use_metaclass(CachedMetaClass, Executed)): class Instance(use_metaclass(CachedMetaClass, Executed)):
""" """
@@ -132,7 +128,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)):
func = self.get_subscope_by_name('__init__') func = self.get_subscope_by_name('__init__')
except KeyError: except KeyError:
return None 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): def _get_func_self_name(self, func):
""" """
@@ -257,12 +253,6 @@ class Instance(use_metaclass(CachedMetaClass, Executed)):
def name(self): def name(self):
return ContextName(self, self.base.name) 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): def __repr__(self):
dec = '' dec = ''
if self.decorates is not None: 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) return InstanceName(var, parent)
elif var.type != 'funcdef' \ elif var.type != 'funcdef' \
and isinstance(var, (Instance, compiled.CompiledObject, tree.Leaf, and isinstance(var, (Instance, compiled.CompiledObject, tree.Leaf,
tree.Module, FunctionExecution)): tree.Module, FunctionExecutionContext)):
return var return var
var = evaluator.wrap(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], return get_instance_el(self._evaluator, self.instance, self.var[index],
self.is_class_var) self.is_class_var)
def __getattr__(self, name):
return getattr(self.var, name)
def isinstance(self, *cls): def isinstance(self, *cls):
return isinstance(self.var, cls) return isinstance(self.var, cls)
@@ -496,7 +483,7 @@ class Wrapper(tree.Base):
return ContextName(self, name) 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 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). 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) 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. Needed because of decorators. Decorators are evaluated here.
""" """
@@ -690,21 +677,12 @@ class Function(use_metaclass(CachedMetaClass, Context, Wrapper)):
if self.base.is_generator(): if self.base.is_generator():
return set([iterable.Generator(self._evaluator, self, params)]) return set([iterable.Generator(self._evaluator, self, params)])
else: else:
return FunctionExecution(self._evaluator, self.parent_context, self, params).get_return_types() return FunctionExecutionContext(
self._evaluator,
@memoize_default() self.parent_context,
def py__annotations__(self): self.base,
parser_func = self.base params
return_annotation = parser_func.annotation() ).get_return_types()
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
def py__class__(self): def py__class__(self):
# This differentiation is only necessary for Python2. Python3 does not # This differentiation is only necessary for Python2. Python3 does not
@@ -715,9 +693,6 @@ class Function(use_metaclass(CachedMetaClass, Context, Wrapper)):
name = 'FUNCTION_CLASS' name = 'FUNCTION_CLASS'
return compiled.get_special_object(self._evaluator, name) return compiled.get_special_object(self._evaluator, name)
def __getattr__(self, name):
return getattr(self.base_func, name)
def __repr__(self): def __repr__(self):
dec = '' dec = ''
if self.decorates is not None: if self.decorates is not None:
@@ -730,7 +705,7 @@ class LambdaWrapper(Function):
return self return self
class FunctionExecution(Executed): class FunctionExecutionContext(Executed):
""" """
This class is used to evaluate functions and their returns. This class is used to evaluate functions and their returns.
@@ -741,10 +716,9 @@ class FunctionExecution(Executed):
""" """
type = 'funcdef' type = 'funcdef'
def __init__(self, evaluator, parent_context, base, var_args): def __init__(self, evaluator, parent_context, funcdef, var_args):
super(FunctionExecution, self).__init__(evaluator, parent_context, base, var_args) super(FunctionExecutionContext, self).__init__(evaluator, parent_context, var_args)
self._copy_dict = {} self._funcdef = funcdef
self._original_function = funcdef = base
if isinstance(funcdef, mixed.MixedObject): if isinstance(funcdef, mixed.MixedObject):
# The extra information in mixed is not needed anymore. We can just # The extra information in mixed is not needed anymore. We can just
# unpack it and give it the tree object. # unpack it and give it the tree object.
@@ -763,11 +737,11 @@ class FunctionExecution(Executed):
@memoize_default(default=set()) @memoize_default(default=set())
@recursion.execution_recursion_decorator @recursion.execution_recursion_decorator
def get_return_types(self, check_yields=False): def get_return_types(self, check_yields=False):
func = self.base funcdef = self._funcdef
if funcdef.type in ('lambdef', 'lambdef_nocond'):
if func.isinstance(LambdaWrapper):
return self._evaluator.eval_element(self.children[-1]) return self._evaluator.eval_element(self.children[-1])
"""
if func.listeners: if func.listeners:
# Feed the listeners, with the params. # Feed the listeners, with the params.
for listener in func.listeners: for listener in func.listeners:
@@ -776,16 +750,19 @@ class FunctionExecution(Executed):
# execution ongoing. In this case Jedi is interested in the # execution ongoing. In this case Jedi is interested in the
# inserted params, not in the actual execution of the function. # inserted params, not in the actual execution of the function.
return set() return set()
"""
if check_yields: if check_yields:
types = set() types = set()
returns = self.yields returns = funcdef.yields
else: else:
returns = self.returns returns = funcdef.returns
types = set(docstrings.find_return_types(self._evaluator, func)) types = set(docstrings.find_return_types(self._evaluator, funcdef))
types |= set(pep0484.find_return_types(self._evaluator, func)) types |= set(pep0484.find_return_types(self._evaluator, funcdef))
for r in returns: for r in returns:
types |= self.eval_node(r.children[1])
continue # TODO REMOVE
check = flow_analysis.break_check(self._evaluator, self, r) check = flow_analysis.break_check(self._evaluator, self, r)
if check is flow_analysis.UNREACHABLE: if check is flow_analysis.UNREACHABLE:
debug.dbg('Return unreachable: %s', r) debug.dbg('Return unreachable: %s', r)
@@ -793,26 +770,26 @@ class FunctionExecution(Executed):
if check_yields: if check_yields:
types |= iterable.unite(self._eval_yield(r)) types |= iterable.unite(self._eval_yield(r))
else: else:
types |= self._evaluator.eval_element(r.children[1]) types |= self.eval_node(r.children[1])
if check is flow_analysis.REACHABLE: if check is flow_analysis.REACHABLE:
debug.dbg('Return reachable: %s', r) debug.dbg('Return reachable: %s', r)
break break
return types return types
def _eval_yield(self, yield_expr): def _eval_yield(self, yield_expr):
element = yield_expr.children[1] node = yield_expr.children[1]
if element.type == 'yield_arg': if node.type == 'yield_arg':
# It must be a yield from. # It must be a yield from.
yield_from_types = self._evaluator.eval_element(element.children[1]) yield_from_types = self.eval_node(node)
for result in iterable.py__iter__(self._evaluator, yield_from_types, element): for result in iterable.py__iter__(self._evaluator, yield_from_types, node):
yield result yield result
else: else:
yield self._evaluator.eval_element(element) yield self.eval_node(node)
@recursion.execution_recursion_decorator @recursion.execution_recursion_decorator
def get_yield_types(self): def get_yield_types(self):
yields = self.yields 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] for_parents = [(x, x.get_parent_until((stopAt))) for x in yields]
# Calculate if the yields are placed within the same for loop. # 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] del evaluator.predefined_if_name_dict_dict[for_stmt]
def get_filters(self, search_global, until_position=None, origin_scope=None): def get_filters(self, search_global, until_position=None, origin_scope=None):
yield FunctionExecutionFilter(self._evaluator, self, self._original_function, yield FunctionExecutionFilter(self._evaluator, self, self._funcdef,
self._copied_funcdef,
self.param_by_name, self.param_by_name,
until_position, until_position,
origin_scope=origin_scope) origin_scope=origin_scope)
@memoize_default(default=NO_DEFAULT) @memoize_default(default=NO_DEFAULT)
def _get_params(self): def _get_params(self):
""" return param.get_params(self._evaluator, self._funcdef, self.var_args)
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)
def param_by_name(self, name): def param_by_name(self, name):
return [n for n in self._get_params() if str(n) == name][0] 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): 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): class GlobalName(helpers.FakeName):
@@ -919,7 +861,7 @@ class GlobalName(helpers.FakeName):
name.start_pos, is_definition=True) name.start_pos, is_definition=True)
class ModuleContext(use_metaclass(CachedMetaClass, tree.Module, Wrapper)): class ModuleContext(use_metaclass(CachedMetaClass, TreeContext, Wrapper)):
parent_context = None parent_context = None
def __init__(self, evaluator, module, parent_module=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): def py__class__(self):
return compiled.get_special_object(self._evaluator, 'MODULE_CLASS') return compiled.get_special_object(self._evaluator, 'MODULE_CLASS')
def __getattr__(self, name):
return getattr(self._module, name)
def __repr__(self): def __repr__(self):
return "<%s: %s>" % (type(self).__name__, self._module) return "<%s: %s>" % (type(self).__name__, self._module)

View File

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