diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 62c16e08..d5e2930f 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -83,7 +83,7 @@ class NameFinder(object): check = flow_analysis.break_check(self._evaluator, scope, name.get_definition(), - self.scope) + self.name_str.get_definition().parent) if check is not flow_analysis.UNREACHABLE: last_names.append(name) if check is flow_analysis.REACHABLE: diff --git a/jedi/evaluate/flow_analysis.py b/jedi/evaluate/flow_analysis.py index 783a044a..5f7f03c6 100644 --- a/jedi/evaluate/flow_analysis.py +++ b/jedi/evaluate/flow_analysis.py @@ -34,16 +34,18 @@ UNSURE = Status(None, 'unsure') def break_check(evaluator, base_scope, stmt, origin_scope=None): from jedi.evaluate.representation import wrap - element_scope = wrap(evaluator, stmt.parent) + element_scope = wrap(evaluator, stmt.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. - s = origin_scope - while s is not None: - if element_scope == s: + # 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 element_scope == origin_scope: return REACHABLE - s = s.parent + origin_scope = origin_scope.parent return _break_check(evaluator, stmt, base_scope, element_scope) diff --git a/jedi/parser/representation.py b/jedi/parser/representation.py index 5bb6bbcd..d8fd5b5e 100644 --- a/jedi/parser/representation.py +++ b/jedi/parser/representation.py @@ -155,12 +155,14 @@ class Base(object): scope = scope.parent return scope - def get_parent_scope(self): + def get_parent_scope(self, include_flows=False): """ Returns the underlying scope. """ scope = self.parent while scope.parent is not None: + if include_flows and isinstance(scope, Flow): + return scope if scope.is_scope(): break scope = scope.parent @@ -466,13 +468,17 @@ class Scope(Simple, DocstringMixin): def subscopes(self): return self._search_in_scope(Scope) + @property + def flows(self): + return self._search_in_scope(Flow) + def _search_in_scope(self, typ): def scan(children): elements = [] for element in children: if isinstance(element, typ): elements.append(element) - elif is_node(element, 'suite') or is_node(element, 'simple_stmt') \ + if is_node(element, 'suite') or is_node(element, 'simple_stmt') \ or isinstance(element, Flow): elements += scan(element.children) return elements diff --git a/jedi/parser/user_context.py b/jedi/parser/user_context.py index 86377c1f..e639b3ad 100644 --- a/jedi/parser/user_context.py +++ b/jedi/parser/user_context.py @@ -7,7 +7,7 @@ from jedi import common from jedi.parser import tokenize from jedi._compatibility import u from jedi.parser.fast import FastParser -from jedi.parser import representation +from jedi.parser import representation as pr from jedi import debug from jedi.common import PushBackIterator @@ -244,12 +244,17 @@ class UserContextParser(object): @cache.underscore_memoization def user_scope(self): + """ + Returns the scope in which the user resides. This includes flows. + """ user_stmt = self.user_stmt() if user_stmt is None: def scan(scope): - for s in scope.statements + scope.subscopes: - if isinstance(s, representation.Scope): + for s in scope.subscopes + list(reversed(scope.flows)): + if isinstance(s, (pr.Scope, pr.Flow)): if s.start_pos <= self._position <= s.end_pos: + if isinstance(s, pr.Flow): + return s return scan(s) or s return scan(self.module()) or self.module()