forked from VimPlug/jedi
More tests and better understanding of if/try branches name resolution.
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user