mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-11 00:01:54 +08:00
evaluator class introduction
This commit is contained in:
@@ -202,7 +202,8 @@ def get_names_of_scope(scope, position=None, star_search=True,
|
||||
yield builtin_scope, builtin_scope.get_defined_names()
|
||||
|
||||
|
||||
def find_name(scope, name_str, position=None, search_global=False,
|
||||
class Evaluator(object):
|
||||
def find_name(self, scope, name_str, position=None, search_global=False,
|
||||
is_goto=False, resolve_decorator=True):
|
||||
"""
|
||||
This is the search function. The most important part to debug.
|
||||
@@ -233,7 +234,7 @@ def find_name(scope, name_str, position=None, search_global=False,
|
||||
if r.is_global():
|
||||
for token_name in r.token_list[1:]:
|
||||
if isinstance(token_name, pr.Name):
|
||||
add = find_name(r.parent, str(token_name))
|
||||
add = self.find_name(r.parent, str(token_name))
|
||||
else:
|
||||
# generated objects are used within executions, but these
|
||||
# objects are in functions, and we have to dynamically
|
||||
@@ -263,7 +264,7 @@ def find_name(scope, name_str, position=None, search_global=False,
|
||||
if c in ('*', '**'):
|
||||
t = 'tuple' if c == '*' else 'dict'
|
||||
res_new = [er.Instance(
|
||||
find_name(builtin.Builtin.scope, t)[0])
|
||||
self.find_name(builtin.Builtin.scope, t)[0])
|
||||
]
|
||||
if not r.assignment_details:
|
||||
# this means that there are no default params,
|
||||
@@ -275,7 +276,7 @@ def find_name(scope, name_str, position=None, search_global=False,
|
||||
#if r.docstr:
|
||||
#res_new.append(r)
|
||||
|
||||
scopes = follow_statement(r, seek_name=name_str)
|
||||
scopes = self.follow_statement(r, seek_name=name_str)
|
||||
add += remove_statements(scopes)
|
||||
|
||||
if check_instance is not None:
|
||||
@@ -305,7 +306,7 @@ def find_name(scope, name_str, position=None, search_global=False,
|
||||
# one, remember `in`). And follow it.
|
||||
if not loop.inputs:
|
||||
return []
|
||||
result = get_iterator_types(follow_statement(loop.inputs[0]))
|
||||
result = get_iterator_types(self.follow_statement(loop.inputs[0]))
|
||||
if len(loop.set_vars) > 1:
|
||||
commands = loop.set_stmt.get_commands()
|
||||
# loops with loop.set_vars > 0 only have one command
|
||||
@@ -471,6 +472,233 @@ def find_name(scope, name_str, position=None, search_global=False,
|
||||
return filter_name(scope_generator)
|
||||
return descriptor_check(remove_statements(filter_name(scope_generator)))
|
||||
|
||||
@recursion.RecursionDecorator
|
||||
@cache.memoize_default(default=())
|
||||
def follow_statement(self, stmt, seek_name=None):
|
||||
"""
|
||||
The starting point of the completion. A statement always owns a call list,
|
||||
which are the calls, that a statement does.
|
||||
In case multiple names are defined in the statement, `seek_name` returns
|
||||
the result for this name.
|
||||
|
||||
:param stmt: A `pr.Statement`.
|
||||
:param seek_name: A string.
|
||||
"""
|
||||
debug.dbg('follow_stmt %s (%s)' % (stmt, seek_name))
|
||||
commands = stmt.get_commands()
|
||||
debug.dbg('calls: %s' % commands)
|
||||
|
||||
result = self.follow_call_list(commands)
|
||||
|
||||
# Assignment checking is only important if the statement defines multiple
|
||||
# variables.
|
||||
if len(stmt.get_set_vars()) > 1 and seek_name and stmt.assignment_details:
|
||||
new_result = []
|
||||
for ass_commands, op in stmt.assignment_details:
|
||||
new_result += find_assignments(ass_commands[0], result, seek_name)
|
||||
result = new_result
|
||||
return set(result)
|
||||
|
||||
@common.rethrow_uncaught
|
||||
def follow_call_list(self, call_list, follow_array=False):
|
||||
"""
|
||||
`call_list` can be either `pr.Array` or `list of list`.
|
||||
It is used to evaluate a two dimensional object, that has calls, arrays and
|
||||
operators in it.
|
||||
"""
|
||||
def evaluate_list_comprehension(lc, parent=None):
|
||||
input = lc.input
|
||||
nested_lc = lc.input.token_list[0]
|
||||
if isinstance(nested_lc, pr.ListComprehension):
|
||||
# is nested LC
|
||||
input = nested_lc.stmt
|
||||
module = input.get_parent_until()
|
||||
# create a for loop, which does the same as list comprehensions
|
||||
loop = pr.ForFlow(module, [input], lc.stmt.start_pos, lc.middle, True)
|
||||
|
||||
loop.parent = parent or lc.get_parent_until(pr.IsScope)
|
||||
|
||||
if isinstance(nested_lc, pr.ListComprehension):
|
||||
loop = evaluate_list_comprehension(nested_lc, loop)
|
||||
return loop
|
||||
|
||||
result = []
|
||||
calls_iterator = iter(call_list)
|
||||
for call in calls_iterator:
|
||||
if pr.Array.is_type(call, pr.Array.NOARRAY):
|
||||
r = list(itertools.chain.from_iterable(self.follow_statement(s)
|
||||
for s in call))
|
||||
call_path = call.generate_call_path()
|
||||
next(call_path, None) # the first one has been used already
|
||||
result += self.follow_paths(call_path, r, call.parent,
|
||||
position=call.start_pos)
|
||||
elif isinstance(call, pr.ListComprehension):
|
||||
loop = evaluate_list_comprehension(call)
|
||||
# Caveat: parents are being changed, but this doesn't matter,
|
||||
# because nothing else uses it.
|
||||
call.stmt.parent = loop
|
||||
result += self.follow_statement(call.stmt)
|
||||
else:
|
||||
if isinstance(call, pr.Lambda):
|
||||
result.append(er.Function(call))
|
||||
# With things like params, these can also be functions...
|
||||
elif isinstance(call, pr.Base) and call.isinstance(er.Function,
|
||||
er.Class, er.Instance, dynamic.ArrayInstance):
|
||||
result.append(call)
|
||||
# The string tokens are just operations (+, -, etc.)
|
||||
elif not isinstance(call, (str, unicode)):
|
||||
if isinstance(call, pr.Call) and str(call.name) == 'if':
|
||||
# Ternary operators.
|
||||
while True:
|
||||
try:
|
||||
call = next(calls_iterator)
|
||||
except StopIteration:
|
||||
break
|
||||
with common.ignored(AttributeError):
|
||||
if str(call.name) == 'else':
|
||||
break
|
||||
continue
|
||||
result += self.follow_call(call)
|
||||
elif call == '*':
|
||||
if [r for r in result if isinstance(r, er.Array)
|
||||
or isinstance(r, er.Instance)
|
||||
and str(r.name) == 'str']:
|
||||
# if it is an iterable, ignore * operations
|
||||
next(calls_iterator)
|
||||
return set(result)
|
||||
|
||||
def follow_call(self, call):
|
||||
"""Follow a call is following a function, variable, string, etc."""
|
||||
path = call.generate_call_path()
|
||||
|
||||
# find the statement of the Scope
|
||||
s = call
|
||||
while not s.parent.isinstance(pr.IsScope):
|
||||
s = s.parent
|
||||
return self.follow_call_path(path, s.parent, s.start_pos)
|
||||
|
||||
def follow_call_path(self, path, scope, position):
|
||||
"""Follows a path generated by `pr.StatementElement.generate_call_path()`"""
|
||||
current = next(path)
|
||||
|
||||
if isinstance(current, pr.Array):
|
||||
result = [er.Array(current)]
|
||||
else:
|
||||
if isinstance(current, pr.NamePart):
|
||||
# This is the first global lookup.
|
||||
scopes = self.find_name(scope, current, position=position,
|
||||
search_global=True)
|
||||
else:
|
||||
# for pr.Literal
|
||||
scopes = self.find_name(builtin.Builtin.scope, current.type_as_string())
|
||||
# Make instances of those number/string objects.
|
||||
scopes = [er.Instance(s, (current.value,)) for s in scopes]
|
||||
result = imports.strip_imports(scopes)
|
||||
|
||||
return self.follow_paths(path, result, scope, position=position)
|
||||
|
||||
def follow_paths(self, path, results, call_scope, position=None):
|
||||
"""
|
||||
In each result, `path` must be followed. Copies the path iterator.
|
||||
"""
|
||||
results_new = []
|
||||
if results:
|
||||
if len(results) > 1:
|
||||
iter_paths = itertools.tee(path, len(results))
|
||||
else:
|
||||
iter_paths = [path]
|
||||
|
||||
for i, r in enumerate(results):
|
||||
fp = self.follow_path(iter_paths[i], r, call_scope, position=position)
|
||||
if fp is not None:
|
||||
results_new += fp
|
||||
else:
|
||||
# This means stop iteration.
|
||||
return results
|
||||
return results_new
|
||||
|
||||
def follow_path(self, path, scope, call_scope, position=None):
|
||||
"""
|
||||
Uses a generator and tries to complete the path, e.g.::
|
||||
|
||||
foo.bar.baz
|
||||
|
||||
`follow_path` is only responsible for completing `.bar.baz`, the rest is
|
||||
done in the `follow_call` function.
|
||||
"""
|
||||
# current is either an Array or a Scope.
|
||||
try:
|
||||
current = next(path)
|
||||
except StopIteration:
|
||||
return None
|
||||
debug.dbg('follow %s in scope %s' % (current, scope))
|
||||
|
||||
result = []
|
||||
if isinstance(current, pr.Array):
|
||||
# This must be an execution, either () or [].
|
||||
if current.type == pr.Array.LIST:
|
||||
if hasattr(scope, 'get_index_types'):
|
||||
result = scope.get_index_types(current)
|
||||
elif current.type not in [pr.Array.DICT]:
|
||||
# Scope must be a class or func - make an instance or execution.
|
||||
debug.dbg('exe', scope)
|
||||
result = er.Execution(scope, current).get_return_types()
|
||||
else:
|
||||
# Curly braces are not allowed, because they make no sense.
|
||||
debug.warning('strange function call with {}', current, scope)
|
||||
else:
|
||||
# The function must not be decorated with something else.
|
||||
if scope.isinstance(er.Function):
|
||||
scope = scope.get_magic_method_scope()
|
||||
else:
|
||||
# This is the typical lookup while chaining things.
|
||||
if filter_private_variable(scope, call_scope, current):
|
||||
return []
|
||||
result = imports.strip_imports(self.find_name(scope, current,
|
||||
position=position))
|
||||
return self.follow_paths(path, set(result), call_scope, position=position)
|
||||
|
||||
def goto(self, stmt, call_path=None):
|
||||
if call_path is None:
|
||||
commands = stmt.get_commands()
|
||||
if len(commands) == 0:
|
||||
return [], ''
|
||||
# Only the first command is important, the rest should basically not
|
||||
# happen except in broken code (e.g. docstrings that aren't code).
|
||||
call = commands[0]
|
||||
if isinstance(call, (str, unicode)):
|
||||
call_path = [call]
|
||||
else:
|
||||
call_path = list(call.generate_call_path())
|
||||
|
||||
scope = stmt.get_parent_until(pr.IsScope)
|
||||
pos = stmt.start_pos
|
||||
call_path, search = call_path[:-1], call_path[-1]
|
||||
pos = pos[0], pos[1] + 1
|
||||
|
||||
if call_path:
|
||||
scopes = self.follow_call_path(iter(call_path), scope, pos)
|
||||
search_global = False
|
||||
pos = None
|
||||
else:
|
||||
scopes = [scope]
|
||||
search_global = True
|
||||
follow_res = []
|
||||
for s in scopes:
|
||||
follow_res += self.find_name(s, search, pos,
|
||||
search_global=search_global, is_goto=True)
|
||||
return follow_res, search
|
||||
|
||||
|
||||
def filter_private_variable(scope, call_scope, var_name):
|
||||
"""private variables begin with a double underline `__`"""
|
||||
if isinstance(var_name, (str, unicode)) and isinstance(scope, er.Instance)\
|
||||
and var_name.startswith('__') and not var_name.endswith('__'):
|
||||
s = call_scope.get_parent_until((pr.Class, er.Instance))
|
||||
if s != scope and s != scope.base.base:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_getattr(inst, name_str):
|
||||
"""Checks for both __getattr__ and __getattribute__ methods"""
|
||||
@@ -590,237 +818,3 @@ def find_assignments(lhs, results, seek_name):
|
||||
return results
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
@recursion.RecursionDecorator
|
||||
@cache.memoize_default(default=())
|
||||
def follow_statement(stmt, seek_name=None):
|
||||
"""
|
||||
The starting point of the completion. A statement always owns a call list,
|
||||
which are the calls, that a statement does.
|
||||
In case multiple names are defined in the statement, `seek_name` returns
|
||||
the result for this name.
|
||||
|
||||
:param stmt: A `pr.Statement`.
|
||||
:param seek_name: A string.
|
||||
"""
|
||||
debug.dbg('follow_stmt %s (%s)' % (stmt, seek_name))
|
||||
commands = stmt.get_commands()
|
||||
debug.dbg('calls: %s' % commands)
|
||||
|
||||
result = follow_call_list(commands)
|
||||
|
||||
# Assignment checking is only important if the statement defines multiple
|
||||
# variables.
|
||||
if len(stmt.get_set_vars()) > 1 and seek_name and stmt.assignment_details:
|
||||
new_result = []
|
||||
for ass_commands, op in stmt.assignment_details:
|
||||
new_result += find_assignments(ass_commands[0], result, seek_name)
|
||||
result = new_result
|
||||
return set(result)
|
||||
|
||||
|
||||
@common.rethrow_uncaught
|
||||
def follow_call_list(call_list, follow_array=False):
|
||||
"""
|
||||
`call_list` can be either `pr.Array` or `list of list`.
|
||||
It is used to evaluate a two dimensional object, that has calls, arrays and
|
||||
operators in it.
|
||||
"""
|
||||
def evaluate_list_comprehension(lc, parent=None):
|
||||
input = lc.input
|
||||
nested_lc = lc.input.token_list[0]
|
||||
if isinstance(nested_lc, pr.ListComprehension):
|
||||
# is nested LC
|
||||
input = nested_lc.stmt
|
||||
module = input.get_parent_until()
|
||||
# create a for loop, which does the same as list comprehensions
|
||||
loop = pr.ForFlow(module, [input], lc.stmt.start_pos, lc.middle, True)
|
||||
|
||||
loop.parent = parent or lc.get_parent_until(pr.IsScope)
|
||||
|
||||
if isinstance(nested_lc, pr.ListComprehension):
|
||||
loop = evaluate_list_comprehension(nested_lc, loop)
|
||||
return loop
|
||||
|
||||
result = []
|
||||
calls_iterator = iter(call_list)
|
||||
for call in calls_iterator:
|
||||
if pr.Array.is_type(call, pr.Array.NOARRAY):
|
||||
r = list(itertools.chain.from_iterable(follow_statement(s)
|
||||
for s in call))
|
||||
call_path = call.generate_call_path()
|
||||
next(call_path, None) # the first one has been used already
|
||||
result += follow_paths(call_path, r, call.parent,
|
||||
position=call.start_pos)
|
||||
elif isinstance(call, pr.ListComprehension):
|
||||
loop = evaluate_list_comprehension(call)
|
||||
# Caveat: parents are being changed, but this doesn't matter,
|
||||
# because nothing else uses it.
|
||||
call.stmt.parent = loop
|
||||
result += follow_statement(call.stmt)
|
||||
else:
|
||||
if isinstance(call, pr.Lambda):
|
||||
result.append(er.Function(call))
|
||||
# With things like params, these can also be functions...
|
||||
elif isinstance(call, pr.Base) and call.isinstance(er.Function,
|
||||
er.Class, er.Instance, dynamic.ArrayInstance):
|
||||
result.append(call)
|
||||
# The string tokens are just operations (+, -, etc.)
|
||||
elif not isinstance(call, (str, unicode)):
|
||||
if isinstance(call, pr.Call) and str(call.name) == 'if':
|
||||
# Ternary operators.
|
||||
while True:
|
||||
try:
|
||||
call = next(calls_iterator)
|
||||
except StopIteration:
|
||||
break
|
||||
with common.ignored(AttributeError):
|
||||
if str(call.name) == 'else':
|
||||
break
|
||||
continue
|
||||
result += follow_call(call)
|
||||
elif call == '*':
|
||||
if [r for r in result if isinstance(r, er.Array)
|
||||
or isinstance(r, er.Instance)
|
||||
and str(r.name) == 'str']:
|
||||
# if it is an iterable, ignore * operations
|
||||
next(calls_iterator)
|
||||
return set(result)
|
||||
|
||||
|
||||
def follow_call(call):
|
||||
"""Follow a call is following a function, variable, string, etc."""
|
||||
path = call.generate_call_path()
|
||||
|
||||
# find the statement of the Scope
|
||||
s = call
|
||||
while not s.parent.isinstance(pr.IsScope):
|
||||
s = s.parent
|
||||
return follow_call_path(path, s.parent, s.start_pos)
|
||||
|
||||
|
||||
def follow_call_path(path, scope, position):
|
||||
"""Follows a path generated by `pr.StatementElement.generate_call_path()`"""
|
||||
current = next(path)
|
||||
|
||||
if isinstance(current, pr.Array):
|
||||
result = [er.Array(current)]
|
||||
else:
|
||||
if isinstance(current, pr.NamePart):
|
||||
# This is the first global lookup.
|
||||
scopes = find_name(scope, current, position=position,
|
||||
search_global=True)
|
||||
else:
|
||||
# for pr.Literal
|
||||
scopes = find_name(builtin.Builtin.scope, current.type_as_string())
|
||||
# Make instances of those number/string objects.
|
||||
scopes = [er.Instance(s, (current.value,)) for s in scopes]
|
||||
result = imports.strip_imports(scopes)
|
||||
|
||||
return follow_paths(path, result, scope, position=position)
|
||||
|
||||
|
||||
def follow_paths(path, results, call_scope, position=None):
|
||||
"""
|
||||
In each result, `path` must be followed. Copies the path iterator.
|
||||
"""
|
||||
results_new = []
|
||||
if results:
|
||||
if len(results) > 1:
|
||||
iter_paths = itertools.tee(path, len(results))
|
||||
else:
|
||||
iter_paths = [path]
|
||||
|
||||
for i, r in enumerate(results):
|
||||
fp = follow_path(iter_paths[i], r, call_scope, position=position)
|
||||
if fp is not None:
|
||||
results_new += fp
|
||||
else:
|
||||
# This means stop iteration.
|
||||
return results
|
||||
return results_new
|
||||
|
||||
|
||||
def follow_path(path, scope, call_scope, position=None):
|
||||
"""
|
||||
Uses a generator and tries to complete the path, e.g.::
|
||||
|
||||
foo.bar.baz
|
||||
|
||||
`follow_path` is only responsible for completing `.bar.baz`, the rest is
|
||||
done in the `follow_call` function.
|
||||
"""
|
||||
# current is either an Array or a Scope.
|
||||
try:
|
||||
current = next(path)
|
||||
except StopIteration:
|
||||
return None
|
||||
debug.dbg('follow %s in scope %s' % (current, scope))
|
||||
|
||||
result = []
|
||||
if isinstance(current, pr.Array):
|
||||
# This must be an execution, either () or [].
|
||||
if current.type == pr.Array.LIST:
|
||||
if hasattr(scope, 'get_index_types'):
|
||||
result = scope.get_index_types(current)
|
||||
elif current.type not in [pr.Array.DICT]:
|
||||
# Scope must be a class or func - make an instance or execution.
|
||||
debug.dbg('exe', scope)
|
||||
result = er.Execution(scope, current).get_return_types()
|
||||
else:
|
||||
# Curly braces are not allowed, because they make no sense.
|
||||
debug.warning('strange function call with {}', current, scope)
|
||||
else:
|
||||
# The function must not be decorated with something else.
|
||||
if scope.isinstance(er.Function):
|
||||
scope = scope.get_magic_method_scope()
|
||||
else:
|
||||
# This is the typical lookup while chaining things.
|
||||
if filter_private_variable(scope, call_scope, current):
|
||||
return []
|
||||
result = imports.strip_imports(find_name(scope, current,
|
||||
position=position))
|
||||
return follow_paths(path, set(result), call_scope, position=position)
|
||||
|
||||
|
||||
def filter_private_variable(scope, call_scope, var_name):
|
||||
"""private variables begin with a double underline `__`"""
|
||||
if isinstance(var_name, (str, unicode)) and isinstance(scope, er.Instance)\
|
||||
and var_name.startswith('__') and not var_name.endswith('__'):
|
||||
s = call_scope.get_parent_until((pr.Class, er.Instance))
|
||||
if s != scope and s != scope.base.base:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def goto(stmt, call_path=None):
|
||||
if call_path is None:
|
||||
commands = stmt.get_commands()
|
||||
if len(commands) == 0:
|
||||
return [], ''
|
||||
# Only the first command is important, the rest should basically not
|
||||
# happen except in broken code (e.g. docstrings that aren't code).
|
||||
call = commands[0]
|
||||
if isinstance(call, (str, unicode)):
|
||||
call_path = [call]
|
||||
else:
|
||||
call_path = list(call.generate_call_path())
|
||||
|
||||
scope = stmt.get_parent_until(pr.IsScope)
|
||||
pos = stmt.start_pos
|
||||
call_path, search = call_path[:-1], call_path[-1]
|
||||
pos = pos[0], pos[1] + 1
|
||||
|
||||
if call_path:
|
||||
scopes = follow_call_path(iter(call_path), scope, pos)
|
||||
search_global = False
|
||||
pos = None
|
||||
else:
|
||||
scopes = [scope]
|
||||
search_global = True
|
||||
follow_res = []
|
||||
for s in scopes:
|
||||
follow_res += find_name(s, search, pos,
|
||||
search_global=search_global, is_goto=True)
|
||||
return follow_res, search
|
||||
|
||||
Reference in New Issue
Block a user