diff --git a/jedi/evaluate/context/module.py b/jedi/evaluate/context/module.py index eec8b9ab..bb1c8296 100644 --- a/jedi/evaluate/context/module.py +++ b/jedi/evaluate/context/module.py @@ -216,5 +216,5 @@ class ModuleContext(TreeContext): return "<%s: %s@%s-%s is_stub=%s>" % ( self.__class__.__name__, self._string_name, self.tree_node.start_pos[0], self.tree_node.end_pos[0], - self._path.endswith('.pyi') + self._path is not None and self._path.endswith('.pyi') ) diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 5e7043f7..8b8362be 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -110,7 +110,7 @@ class NameFinder(object): ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef') if ancestor is not None: colon = ancestor.children[-2] - if position < colon.start_pos: + if position is not None and position < colon.start_pos: if lambdef is None or position < lambdef.children[-2].start_pos: position = ancestor.start_pos diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index f23943e1..61236151 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -54,7 +54,7 @@ def _evaluate_annotation_string(context, string, index=None): context_set = context.eval_node(node) if index is not None: context_set = context_set.filter( - lambda context: context.array_type == u'tuple' + lambda context: context.array_type == u'tuple' # noqa and len(list(context.py__iter__())) >= index ).py__getitem__(index) return context_set.execute_evaluated() diff --git a/jedi/evaluate/syntax_tree.py b/jedi/evaluate/syntax_tree.py index 95ccf219..307ab988 100644 --- a/jedi/evaluate/syntax_tree.py +++ b/jedi/evaluate/syntax_tree.py @@ -196,9 +196,17 @@ def eval_atom(context, atom): ) or atom if stmt.type == 'lambdef': stmt = atom + position = stmt.start_pos + if _is_annotation_name(atom): + # Since Python 3.7 (with from __future__ import annotations), + # annotations are essentially strings and can reference objects + # that are defined further down in code. Therefore just set the + # position to None, so the finder will not try to stop at a certain + # position in the module. + position = None return context.py__getattribute__( name_or_str=atom, - position=stmt.start_pos, + position=position, search_global=True ) elif atom.type == 'keyword': @@ -410,6 +418,22 @@ def _eval_comparison(evaluator, context, left_contexts, operator, right_contexts ) +def _is_annotation_name(name): + ancestor = tree.search_ancestor(name, 'param', 'funcdef', 'expr_stmt') + if ancestor is None: + return False + + if ancestor.type in ('param', 'funcdef'): + annotation = ancestor.annotation + if annotation is not None: + return annotation.start_pos <= name.start_pos < annotation.end_pos + elif ancestor.type == 'expr_stmt': + c = ancestor.children + if len(c) > 1 and c[1].type == 'annassign': + return c[1].start_pos <= name.start_pos < c[1].end_pos + return True + + def _is_tuple(context): return isinstance(context, iterable.Sequence) and context.array_type == 'tuple' @@ -497,7 +521,6 @@ def _remove_statements(evaluator, context, stmt, name): def tree_name_to_contexts(evaluator, context, tree_name): - context_set = ContextSet() module_node = context.get_root_context().tree_node if module_node is not None: