Annotations can contain forward references even if they are not a string anymore

Since Python 3.7 this behavior can be imported with from future import __annotations
This commit is contained in:
Dave Halter
2018-07-28 16:35:24 +02:00
parent b073b05aa0
commit 9bba91628a
4 changed files with 28 additions and 5 deletions

View File

@@ -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')
)

View File

@@ -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

View File

@@ -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()

View File

@@ -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: