1
0
forked from VimPlug/jedi

More tests and better understanding of if/try branches name resolution.

This commit is contained in:
Dave Halter
2016-11-29 10:21:50 +01:00
parent 481a917ada
commit 5b81a2375d
5 changed files with 107 additions and 19 deletions

View File

@@ -155,13 +155,18 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
def _filter(self, names):
names = super(ParserTreeFilter, self)._filter(names)
names = [n for n in names if n.is_definition() and n.parent.type != 'trailer']
names = [n for n in names
if ((n.parent if n.parent.type in ('classdef', 'funcdef') else n)
.get_parent_scope() == self._parser_scope)]
names = [n for n in names if self._is_name_reachable(n)]
return list(self._check_flows(names))
def _is_name_reachable(self, name):
if not name.is_definition():
return False
parent = name.parent
if parent.type == 'trailer':
return False
base_node = parent if parent.type in ('classdef', 'funcdef') else name
return base_node.get_parent_scope() == self._parser_scope
def _check_flows(self, names):
for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
check = flow_analysis.reachability_check(

View File

@@ -32,19 +32,46 @@ UNREACHABLE = Status(False, 'unreachable')
UNSURE = Status(None, 'unsure')
def _get_flow_scopes(node):
while True:
node = node.get_parent_scope(include_flows=True)
if node.type in ('funcdef', 'classdef', 'file_input'):
return
yield node
def reachability_check(context, context_scope, node, origin_scope=None):
flow_scope = node.get_parent_scope(include_flows=True)
# Direct parents get resolved, we filter scopes that are separate branches.
# This makes sense for autocompletion and static analysis. For actual
# Python it doesn't matter, because we're talking about potentially
# unreachable code.
# e.g. `if 0:` would cause all name lookup within the flow make
# unaccessible. This is not a "problem" in Python, because the code is
# never called. In Jedi though, we still want to infer types.
#while origin_scope is not None:
#if flow_scope == origin_scope:
#return REACHABLE
#origin_scope = origin_scope.parent
if origin_scope is not None:
origin_flow_scopes = list(_get_flow_scopes(origin_scope))
node_flow_scopes = list(_get_flow_scopes(node))
branch_matches = True
for flow_scope in origin_flow_scopes:
if flow_scope in node_flow_scopes:
node_keyword = flow_scope.get_branch_keyword(node)
origin_keyword = flow_scope.get_branch_keyword(origin_scope)
branch_matches = node_keyword == origin_keyword
if flow_scope.type == 'if_stmt':
if not branch_matches:
return UNREACHABLE
elif flow_scope.type == 'try_stmt':
if not branch_matches and origin_keyword == 'else' \
and node_keyword == 'except':
return UNREACHABLE
break
# Direct parents get resolved, we filter scopes that are separate
# branches. This makes sense for autocompletion and static analysis.
# For actual Python it doesn't matter, because we're talking about
# potentially unreachable code.
# e.g. `if 0:` would cause all name lookup within the flow make
# unaccessible. This is not a "problem" in Python, because the code is
# never called. In Jedi though, we still want to infer types.
while origin_scope is not None:
if flow_scope == origin_scope and branch_matches:
return REACHABLE
origin_scope = origin_scope.parent
return _break_check(context, context_scope, flow_scope, node)

View File

@@ -179,10 +179,13 @@ def builtins_isinstance(evaluator, objects, types, arguments):
for cls_or_tup in types:
if cls_or_tup.is_class():
bool_results.add(cls_or_tup in mro)
elif str(cls_or_tup.name) == 'tuple' \
and cls_or_tup.get_parent_scope() == evaluator.BUILTINS:
elif cls_or_tup.name.string_name == 'tuple' \
and cls_or_tup.get_root_context() == evaluator.BUILTINS:
# Check for tuples.
classes = unite(cls_or_tup.py__iter__())
classes = unite(
lazy_context.infer()
for lazy_context in cls_or_tup.py__iter__()
)
bool_results.add(any(cls in mro for cls in classes))
else:
_, lazy_context = list(arguments.unpack())[1]

View File

@@ -1134,12 +1134,29 @@ class Lambda(Function):
class Flow(BaseNode):
__slots__ = ()
FLOW_KEYWORDS = (
'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
)
def nodes_to_execute(self, last_added=False):
for child in self.children:
for node_to_execute in child.nodes_to_execute():
yield node_to_execute
def get_branch_keyword(self, node):
start_pos = node.start_pos
if not (self.start_pos < start_pos <= self.end_pos):
raise ValueError('The node is not part of the flow.')
keyword = None
for i, child in enumerate(self.children):
if start_pos < child.start_pos:
return keyword
first_leaf = child.first_leaf()
if first_leaf in self.FLOW_KEYWORDS:
keyword = first_leaf
return 0
class IfStmt(Flow):
type = 'if_stmt'

View File

@@ -1,3 +1,39 @@
# -----------------
# First a few name resolution things
# -----------------
x = 3
if NOT_DEFINED:
x = ''
#? 6 int()
elif x:
pass
else:
#? int()
x
x = 1
try:
x = ''
#? 8 int() str()
except x:
#? 5 int() str()
x
x = 1.0
else:
#? 5 int() str()
x
x = list
finally:
#? 5 int() str() float() list
x
x = tuple
# -----------------
# Return checks
# -----------------
def foo(x):
if 1.0:
return 1