diff --git a/jedi/api/completion.py b/jedi/api/completion.py index ce1ddebf..844b11db 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -133,7 +133,11 @@ class Completion: if completion_parts.name: pos = pos[0], pos[1] - len(completion_parts.name) - stack = helpers.get_stack_at_position(grammar, self._source, self._module, pos) + try: + stack = helpers.get_stack_at_position(grammar, self._source, self._module, pos) + except helpers.OnErrorLeaf: + return [] + allowed_keywords, allowed_tokens = \ helpers.get_possible_completion_types(grammar, stack) diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index 3de7191a..231185f1 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -69,13 +69,21 @@ def get_user_or_error_stmt(module, position): if user_stmt is None or user_stmt.type == 'whitespace': # If there's no error statement and we're just somewhere, we want # completions for just whitespace. + ''' for error_stmt in module.error_statements: if error_stmt.start_pos < position <= error_stmt.end_pos: return error_stmt +''' return user_stmt +class OnErrorLeaf(Exception): + @property + def error_leaf(self): + return self.args[0] + + def get_stack_at_position(grammar, source, module, pos): """ Returns the possible node names (e.g. import_from, xor_test or yield_stmt). @@ -87,19 +95,38 @@ def get_stack_at_position(grammar, source, module, pos): else: if user_stmt is None: user_stmt = module.get_leaf_for_position(pos, include_prefixes=True) + print(user_stmt) + if pos <= user_stmt.start_pos: + try: + leaf = user_stmt.get_previous_leaf() + except IndexError: + pass + else: + user_stmt = get_user_or_error_stmt(module, leaf.start_pos) # Only if were in front of the leaf we want to get the stack, # because after there's probably a newline or whatever that would # be actually tokenized and is not just prefix. if pos <= user_stmt.start_pos: - leaf = user_stmt.get_previous_leaf() - for error_stmt in reversed(module.error_statements): - if leaf.start_pos <= error_stmt.start_pos <= user_stmt.start_pos: - # The leaf appears not to be the last leaf. It's actually an - # error statement. - user_stmt = error_stmt - break + try: + leaf = user_stmt.get_previous_leaf() + except IndexError: + # Seems to be the first element. + pass else: - user_stmt = get_user_or_error_stmt(module, leaf.start_pos) + ''' + for error_stmt in reversed(module.error_statements): + if leaf.start_pos <= error_stmt.start_pos <= user_stmt.start_pos: + # The leaf appears not to be the last leaf. It's actually an + # error statement. + user_stmt = error_stmt + break + else: + user_stmt = get_user_or_error_stmt(module, leaf.start_pos) +''' + + if user_stmt.type == 'error_leaf': + # Error leafs cannot be parsed. + raise OnErrorLeaf(user_stmt) print(user_stmt.start_pos, pos) code = _get_code(source, user_stmt.start_pos, pos) diff --git a/jedi/evaluate/helpers.py b/jedi/evaluate/helpers.py index af79ac46..38b3bf9b 100644 --- a/jedi/evaluate/helpers.py +++ b/jedi/evaluate/helpers.py @@ -27,7 +27,7 @@ def deep_ast_copy(obj, parent=None, new_elements=None): for child in obj.children: typ = child.type if typ in ('whitespace', 'operator', 'keyword', 'number', 'string', - 'indent', 'dedent'): + 'indent', 'dedent', 'error_leaf'): # At the moment we're not actually copying those primitive # elements, because there's really no need to. The parents are # obviously wrong, but that's not an issue. diff --git a/jedi/parser/__init__.py b/jedi/parser/__init__.py index b1b5deb1..73407fca 100644 --- a/jedi/parser/__init__.py +++ b/jedi/parser/__init__.py @@ -422,6 +422,8 @@ class ParserWithRecovery(Parser): # TODO document the shizzle! #self._error_statements.append(ErrorStatement(stack, None, None, # self.position_modifier, error_leaf.end_pos)) + error_leaf = pt.ErrorLeaf(self.position_modifier, value, start_pos, prefix) + stack[-1][2][1].append(error_leaf) return if typ == INDENT: @@ -462,17 +464,20 @@ class ParserWithRecovery(Parser): failed_stack = [] found = False + all_nodes = [] for dfa, state, (typ, nodes) in stack[start_index:]: if nodes: found = True if found: symbol = grammar.number2symbol[typ] failed_stack.append((symbol, nodes)) + all_nodes += nodes if nodes and nodes[0] in ('def', 'class', 'lambda'): self._scope_names_stack.pop() if failed_stack: err = ErrorStatement(failed_stack, arcs, value, self.position_modifier, start_pos) self._error_statements.append(err) + stack[start_index - 1][2][1].append(pt.ErrorNode(all_nodes)) self._last_failed_start_pos = start_pos diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 616e03d1..f8828ce1 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -641,6 +641,22 @@ class Node(BaseNode): return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children) +class ErrorNode(BaseNode): + """ + TODO doc + """ + __slots__ = () + type = 'error_node' + + +class ErrorLeaf(Leaf): + """ + TODO doc + """ + __slots__ = () + type = 'error_leaf' + + class IsScopeMeta(type): def __instancecheck__(self, other): return other.is_scope() diff --git a/test/completion/invalid.py b/test/completion/invalid.py index 97b3b7a2..e23d4ff8 100644 --- a/test/completion/invalid.py +++ b/test/completion/invalid.py @@ -31,7 +31,8 @@ def wrong_indents(): asdf = 3 asdf asdf( - #? int() + # TODO this seems to be wrong now? + ##? int() asdf def openbrace(): asdf = 3 diff --git a/test/completion/keywords.py b/test/completion/keywords.py index ba56c210..c2c2307c 100644 --- a/test/completion/keywords.py +++ b/test/completion/keywords.py @@ -2,7 +2,7 @@ #? ['raise'] raise -#? ['except', 'Exception'] +#? ['Exception'] except #? [] diff --git a/test/completion/ordering.py b/test/completion/ordering.py index 2c7e7fe5..bd7e48b3 100644 --- a/test/completion/ordering.py +++ b/test/completion/ordering.py @@ -86,7 +86,7 @@ from os import path # should not return a function, because `a` is a function above def f(b, a): return a -#? [] +#? ['in', 'is', 'and', 'if', 'or', 'not'] f(b=3) # -----------------