diff --git a/jedi/evaluate/filters.py b/jedi/evaluate/filters.py index 886ef254..72ce2dfe 100644 --- a/jedi/evaluate/filters.py +++ b/jedi/evaluate/filters.py @@ -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( diff --git a/jedi/evaluate/flow_analysis.py b/jedi/evaluate/flow_analysis.py index f1de9352..35489dc1 100644 --- a/jedi/evaluate/flow_analysis.py +++ b/jedi/evaluate/flow_analysis.py @@ -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) diff --git a/jedi/evaluate/stdlib.py b/jedi/evaluate/stdlib.py index c942cd01..0b026c46 100644 --- a/jedi/evaluate/stdlib.py +++ b/jedi/evaluate/stdlib.py @@ -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] diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 778b9f62..db0dc000 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -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' diff --git a/test/completion/flow_analysis.py b/test/completion/flow_analysis.py index 2840d9ee..0b330a3e 100644 --- a/test/completion/flow_analysis.py +++ b/test/completion/flow_analysis.py @@ -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