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):
|
def _filter(self, names):
|
||||||
names = super(ParserTreeFilter, self)._filter(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 self._is_name_reachable(n)]
|
||||||
names = [n for n in names
|
|
||||||
if ((n.parent if n.parent.type in ('classdef', 'funcdef') else n)
|
|
||||||
.get_parent_scope() == self._parser_scope)]
|
|
||||||
|
|
||||||
return list(self._check_flows(names))
|
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):
|
def _check_flows(self, names):
|
||||||
for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
|
for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
|
||||||
check = flow_analysis.reachability_check(
|
check = flow_analysis.reachability_check(
|
||||||
|
|||||||
@@ -32,19 +32,46 @@ UNREACHABLE = Status(False, 'unreachable')
|
|||||||
UNSURE = Status(None, 'unsure')
|
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):
|
def reachability_check(context, context_scope, node, origin_scope=None):
|
||||||
flow_scope = node.get_parent_scope(include_flows=True)
|
flow_scope = node.get_parent_scope(include_flows=True)
|
||||||
# Direct parents get resolved, we filter scopes that are separate branches.
|
if origin_scope is not None:
|
||||||
# This makes sense for autocompletion and static analysis. For actual
|
origin_flow_scopes = list(_get_flow_scopes(origin_scope))
|
||||||
# Python it doesn't matter, because we're talking about potentially
|
node_flow_scopes = list(_get_flow_scopes(node))
|
||||||
# unreachable code.
|
|
||||||
# e.g. `if 0:` would cause all name lookup within the flow make
|
branch_matches = True
|
||||||
# unaccessible. This is not a "problem" in Python, because the code is
|
for flow_scope in origin_flow_scopes:
|
||||||
# never called. In Jedi though, we still want to infer types.
|
if flow_scope in node_flow_scopes:
|
||||||
#while origin_scope is not None:
|
node_keyword = flow_scope.get_branch_keyword(node)
|
||||||
#if flow_scope == origin_scope:
|
origin_keyword = flow_scope.get_branch_keyword(origin_scope)
|
||||||
#return REACHABLE
|
branch_matches = node_keyword == origin_keyword
|
||||||
#origin_scope = origin_scope.parent
|
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)
|
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:
|
for cls_or_tup in types:
|
||||||
if cls_or_tup.is_class():
|
if cls_or_tup.is_class():
|
||||||
bool_results.add(cls_or_tup in mro)
|
bool_results.add(cls_or_tup in mro)
|
||||||
elif str(cls_or_tup.name) == 'tuple' \
|
elif cls_or_tup.name.string_name == 'tuple' \
|
||||||
and cls_or_tup.get_parent_scope() == evaluator.BUILTINS:
|
and cls_or_tup.get_root_context() == evaluator.BUILTINS:
|
||||||
# Check for tuples.
|
# 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))
|
bool_results.add(any(cls in mro for cls in classes))
|
||||||
else:
|
else:
|
||||||
_, lazy_context = list(arguments.unpack())[1]
|
_, lazy_context = list(arguments.unpack())[1]
|
||||||
|
|||||||
@@ -1134,12 +1134,29 @@ class Lambda(Function):
|
|||||||
|
|
||||||
class Flow(BaseNode):
|
class Flow(BaseNode):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
FLOW_KEYWORDS = (
|
||||||
|
'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
|
||||||
|
)
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
def nodes_to_execute(self, last_added=False):
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
for node_to_execute in child.nodes_to_execute():
|
for node_to_execute in child.nodes_to_execute():
|
||||||
yield node_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):
|
class IfStmt(Flow):
|
||||||
type = 'if_stmt'
|
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):
|
def foo(x):
|
||||||
if 1.0:
|
if 1.0:
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
Reference in New Issue
Block a user