1
0
forked from VimPlug/jedi

Improve flow analysis a bit.

This commit is contained in:
Dave Halter
2014-11-05 19:18:45 +01:00
parent 9549c2b389
commit 186ce2b70a
6 changed files with 52 additions and 26 deletions

View File

@@ -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:]:

View File

@@ -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'):

View File

@@ -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)

View File

@@ -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
elif flow.command in ('try', 'except', 'finally', 'while'):
return UNSURE return UNSURE
else: # for loop
return REACHABLE

View File

@@ -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:

View File

@@ -845,7 +845,34 @@ class Flow(Simple):
class IfStmt(Flow): class IfStmt(Flow):
pass def check_nodes(self):
"""
Returns all the `test` nodes that are defined as x, here:
if x:
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):