forked from VimPlug/jedi
Improve flow analysis a bit.
This commit is contained in:
@@ -183,6 +183,9 @@ class Evaluator(object):
|
|||||||
debug.dbg('eval_element %s', element)
|
debug.dbg('eval_element %s', element)
|
||||||
if isinstance(element, (pr.Name, pr.Literal)) or pr.is_node(element, 'atom'):
|
if isinstance(element, (pr.Name, pr.Literal)) or pr.is_node(element, 'atom'):
|
||||||
return self._eval_atom(element)
|
return self._eval_atom(element)
|
||||||
|
elif isinstance(element, pr.Keyword):
|
||||||
|
# For False/True/None
|
||||||
|
return [compiled.builtin.get_by_name(element.value)]
|
||||||
elif element.type == python_symbols.power:
|
elif element.type == python_symbols.power:
|
||||||
types = self._eval_atom(element.children[0])
|
types = self._eval_atom(element.children[0])
|
||||||
for trailer in element.children[1:]:
|
for trailer in element.children[1:]:
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ def _check_for_exception_catch(evaluator, jedi_obj, exception, payload=None):
|
|||||||
while obj is not None and not obj.isinstance(pr.Function, pr.Class):
|
while obj is not None and not obj.isinstance(pr.Function, pr.Class):
|
||||||
if obj.isinstance(pr.Flow):
|
if obj.isinstance(pr.Flow):
|
||||||
# try/except catch check
|
# try/except catch check
|
||||||
if obj.command == 'try' and check_try_for_except(obj):
|
if obj.isinstance(pr.TryStmt) and check_try_for_except(obj):
|
||||||
return True
|
return True
|
||||||
# hasattr check
|
# hasattr check
|
||||||
if exception == AttributeError and obj.command in ('if', 'while'):
|
if exception == AttributeError and obj.command in ('if', 'while'):
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class NameFinder(object):
|
|||||||
for name in reversed(sorted(names, key=lambda name: name.start_pos)):
|
for name in reversed(sorted(names, key=lambda name: name.start_pos)):
|
||||||
check = flow_analysis.break_check(self._evaluator,
|
check = flow_analysis.break_check(self._evaluator,
|
||||||
scope,
|
scope,
|
||||||
er.wrap(self._evaluator, scope),
|
name.get_definition(),
|
||||||
self.scope)
|
self.scope)
|
||||||
if check is not flow_analysis.UNREACHABLE:
|
if check is not flow_analysis.UNREACHABLE:
|
||||||
last_names.append(name)
|
last_names.append(name)
|
||||||
@@ -154,7 +154,7 @@ class NameFinder(object):
|
|||||||
else:
|
else:
|
||||||
check = flow_analysis.break_check(self._evaluator,
|
check = flow_analysis.break_check(self._evaluator,
|
||||||
name_list_scope,
|
name_list_scope,
|
||||||
er.wrap(self._evaluator, scope),
|
stmt,
|
||||||
self.scope)
|
self.scope)
|
||||||
if check is not flow_analysis.UNREACHABLE:
|
if check is not flow_analysis.UNREACHABLE:
|
||||||
names.append(name)
|
names.append(name)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from jedi.parser.representation import Flow
|
from jedi.parser import representation as pr
|
||||||
|
|
||||||
|
|
||||||
class Status(object):
|
class Status(object):
|
||||||
@@ -32,10 +32,10 @@ UNREACHABLE = Status(False, 'unreachable')
|
|||||||
UNSURE = Status(None, 'unsure')
|
UNSURE = Status(None, 'unsure')
|
||||||
|
|
||||||
|
|
||||||
def break_check(evaluator, base_scope, element_scope, origin_scope=None):
|
def break_check(evaluator, base_scope, stmt, origin_scope=None):
|
||||||
from jedi.evaluate.representation import wrap
|
from jedi.evaluate.representation import wrap
|
||||||
base_scope = wrap(evaluator, base_scope)
|
base_scope = wrap(evaluator, base_scope)
|
||||||
element_scope = wrap(evaluator, element_scope)
|
element_scope = wrap(evaluator, stmt.parent)
|
||||||
|
|
||||||
# Direct parents get resolved, we filter scopes that are separate branches.
|
# Direct parents get resolved, we filter scopes that are separate branches.
|
||||||
# This makes sense for autocompletion and static analysis. For actual
|
# This makes sense for autocompletion and static analysis. For actual
|
||||||
@@ -48,17 +48,18 @@ def break_check(evaluator, base_scope, element_scope, origin_scope=None):
|
|||||||
s = s.parent
|
s = s.parent
|
||||||
|
|
||||||
reachable = REACHABLE
|
reachable = REACHABLE
|
||||||
if isinstance(element_scope, Flow):
|
if isinstance(element_scope, pr.IfStmt):
|
||||||
if element_scope.command == 'else':
|
if element_scope.node_after_else(stmt):
|
||||||
check_scope = element_scope
|
for check_node in element_scope.check_nodes():
|
||||||
while check_scope.previous is not None:
|
reachable = _check_if(evaluator, check_node)
|
||||||
check_scope = check_scope.previous
|
|
||||||
reachable = _check_flow(evaluator, check_scope)
|
|
||||||
if reachable in (REACHABLE, UNSURE):
|
if reachable in (REACHABLE, UNSURE):
|
||||||
break
|
break
|
||||||
reachable = reachable.invert()
|
reachable = reachable.invert()
|
||||||
else:
|
else:
|
||||||
reachable = _check_flow(evaluator, element_scope)
|
node = element_scope.node_in_which_check_node(stmt)
|
||||||
|
reachable = _check_if(evaluator, node)
|
||||||
|
elif isinstance(element_scope, (pr.TryStmt, pr.WhileStmt)):
|
||||||
|
return UNSURE
|
||||||
|
|
||||||
# Only reachable branches need to be examined further.
|
# Only reachable branches need to be examined further.
|
||||||
if reachable in (UNREACHABLE, UNSURE):
|
if reachable in (UNREACHABLE, UNSURE):
|
||||||
@@ -69,15 +70,10 @@ def break_check(evaluator, base_scope, element_scope, origin_scope=None):
|
|||||||
return reachable
|
return reachable
|
||||||
|
|
||||||
|
|
||||||
def _check_flow(evaluator, flow):
|
def _check_if(evaluator, node):
|
||||||
if flow.command in ('elif', 'if') and flow.inputs:
|
types = evaluator.eval_element(node)
|
||||||
types = evaluator.eval_statement(flow.inputs[0])
|
|
||||||
values = set(x.py__bool__() for x in types)
|
values = set(x.py__bool__() for x in types)
|
||||||
if len(values) == 1:
|
if len(values) == 1:
|
||||||
return Status.lookup_table[values.pop()]
|
return Status.lookup_table[values.pop()]
|
||||||
else:
|
else:
|
||||||
return UNSURE
|
return UNSURE
|
||||||
elif flow.command in ('try', 'except', 'finally', 'while'):
|
|
||||||
return UNSURE
|
|
||||||
else: # for loop
|
|
||||||
return REACHABLE
|
|
||||||
|
|||||||
@@ -581,7 +581,7 @@ class FunctionExecution(Executed):
|
|||||||
|
|
||||||
types = list(docstrings.find_return_types(self._evaluator, func))
|
types = list(docstrings.find_return_types(self._evaluator, func))
|
||||||
for r in self.returns:
|
for r in self.returns:
|
||||||
check = flow_analysis.break_check(self._evaluator, self, r.parent)
|
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)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -845,7 +845,34 @@ class Flow(Simple):
|
|||||||
|
|
||||||
|
|
||||||
class IfStmt(Flow):
|
class IfStmt(Flow):
|
||||||
|
def check_nodes(self):
|
||||||
|
"""
|
||||||
|
Returns all the `test` nodes that are defined as x, here:
|
||||||
|
|
||||||
|
if x:
|
||||||
pass
|
pass
|
||||||
|
elif x:
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
for i, c in enumerate(self.children):
|
||||||
|
if c in ('elif', 'if'):
|
||||||
|
yield self.children[i + 1]
|
||||||
|
|
||||||
|
def node_in_which_check_node(self, node):
|
||||||
|
for check_node in reversed(list(self.check_nodes())):
|
||||||
|
if check_node.start_pos < node.start_pos:
|
||||||
|
return check_node
|
||||||
|
|
||||||
|
def node_after_else(self, node):
|
||||||
|
"""
|
||||||
|
Checks if a node is defined after `else`.
|
||||||
|
"""
|
||||||
|
for c in self.children:
|
||||||
|
if c == 'else':
|
||||||
|
if node.start_pos > c.start_pos:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class WhileStmt(Flow):
|
class WhileStmt(Flow):
|
||||||
|
|||||||
Reference in New Issue
Block a user