From 5081b060162b686948210ef5144b7d143a757253 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 20 Aug 2018 19:51:36 +0200 Subject: [PATCH] Add a first try of implementing the typing module --- jedi/evaluate/base_context.py | 3 + jedi/evaluate/context/typing.py | 176 ++++++++++++++++++++++++++++++++ jedi/evaluate/pep0484.py | 4 +- 3 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 jedi/evaluate/context/typing.py diff --git a/jedi/evaluate/base_context.py b/jedi/evaluate/base_context.py index a0a00dff..f3308d65 100644 --- a/jedi/evaluate/base_context.py +++ b/jedi/evaluate/base_context.py @@ -71,6 +71,9 @@ class Context(BaseContext): return f.filter_name(filters) return f.find(filters, attribute_lookup=not search_global) + def execute_annotation(self): + return execute_evaluated(self) + def create_context(self, node, node_is_context=False, node_is_object=False): return self.evaluator.create_context(self, node, node_is_context, node_is_object) diff --git a/jedi/evaluate/context/typing.py b/jedi/evaluate/context/typing.py new file mode 100644 index 00000000..09770f5a --- /dev/null +++ b/jedi/evaluate/context/typing.py @@ -0,0 +1,176 @@ +""" +We need to somehow work with the typing objects. Since the typing objects are +pretty bare we need to add all the Jedi customizations to make them work as +contexts. +""" + +from jedi import debug +from jedi.evaluate.compiled import builtin_from_name +from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS +from jedi.evaluate.context.iterable import SequenceLiteralContext + +_PROXY_TYPES = 'Optional Union Callable Type ClassVar Tuple Generic Protocol'.split() +_TYPE_ALIAS_TYPES = 'List Dict DefaultDict Set FrozenSet Counter Deque ChainMap'.split() + + +def check(context, name): + + if name in _PROXY_TYPES: + return TypingProxy(context) + elif name in _TYPE_ALIAS_TYPES: + # TODO + raise NotImplementedError + elif name == 'TypeVar': + raise NotImplementedError + return TypeVar(context) + elif name == 'Any': + return Any(context) + elif name == 'TYPE_CHECKING': + # This is needed for e.g. imports that are only available for type + # checking or are in cycles. The user can then check this variable. + return builtin_from_name(context.evaluator, u'True') + elif name == 'overload': + # TODO implement overload + return context + elif name == 'cast': + # TODO implement cast + return context + elif name == 'TypedDict': + # TODO implement + # e.g. Movie = TypedDict('Movie', {'name': str, 'year': int}) + return context + elif name in ('no_type_check', 'no_type_check_decorator'): + # This is not necessary, as long as we are not doing type checking. + return context + return context + + +class _TypingBase(object): + def __init__(self, typing_context): + self._class_context = typing_context + + def __getattr__(self, name): + return getattr(self._class_context, name) + + +class TypingProxy(object): + def py__getitem__(self, index_context, contextualized_node): + return TypingProxyWithIndex(self._class_context, index_context) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self._class_context) + + +class _WithIndexBase(_TypingBase): + def __init__(self, class_context, index_context): + super(_WithIndexBase, self).__init__(class_context) + self._index_context = index_context + + def __repr__(self): + return '%s(%s, %s)' % ( + self.__class__.__name__, + self._class_context, + self._index_context + ) + + def _execute_annotations_for_all_indexes(self): + return ContextSet.from_sets( + _iter_over_arguments(self._index_context) + ).execute_annotation() + + +class TypingProxyWithIndex(_WithIndexBase): + def __init__(self, typing_context, index_context): + self._class_context = typing_context + self._index_context = index_context + + def execute_annotation(self): + name = self._class_context.py__name__() + if name == 'Union': + # This is kind of a special case, because we have Unions (in Jedi + # ContextSets). + return self._execute_annotations_for_all_indexes() + elif name == 'Optional': + # Optional is basically just saying it's either None or the actual + # type. + return ContextSet(self._class_context) \ + | ContextSet(builtin_from_name(self.evaluator, u'None')) + elif name == 'Type': + # The type is actually already given in the index_context + return ContextSet(self._index_context) + elif name == 'ClassVar': + # For now don't do anything here, ClassVars are always used. + return self._class_context.execute_annotation() + + cls = globals()[name] + return cls(self._class_context, self._index_context) + + +def _iter_over_arguments(maybe_tuple_context): + if isinstance(maybe_tuple_context, SequenceLiteralContext): + for lazy_context in maybe_tuple_context.py__iter__(): + yield lazy_context.infer() + else: + yield ContextSet(maybe_tuple_context) + + +class _ContainerBase(_WithIndexBase): + def get_filters(self): + pass + + def _get_getitem_contexts(self, index): + for i, contexts in enumerate(_iter_over_arguments(self._index_context)): + if i == index: + return contexts + + debug.warning('No param #%s found for annotation %s', index, self._index_context) + return NO_CONTEXTS + + +class Callable(_ContainerBase): + def py__call__(self, arguments): + # The 0th index are the arguments. + return self._get_getitem_contexts(1) + + +class Tuple(_ContainerBase): + def _is_homogenous(self): + # To specify a variable-length tuple of homogeneous type, Tuple[T, ...] + # is used. + if isinstance(self._index_context, SequenceLiteralContext): + pass + return False + + def py__simple_getitem__(self, index): + if self._is_homogenous(): + return self._get_getitem_contexts(0) + else: + if isinstance(index, int): + return self._get_getitem_contexts(index) + + debug.dbg('The getitem is') + return NO_CONTEXTS + + def py__getitem__(self): + if self._is_homogenous(): + return self._get_getitem_contexts(0) + + return self._execute_annotations_for_all_indexes() + + +class Generic(_ContainerBase): + # TODO implement typevars + pass + + +# For pure type inference these two classes are basically the same. It's much +# more interesting once you do type checking. +Protocol = Generic + + +class Any(_TypingBase): + def __init__(self): + # Any is basically object, when it comes to type inference/completions. + # This is obviously not correct, but let's just use this for now. + context = ContextSet(builtin_from_name(self.evaluator, u'object')) + super(_WithIndexBase, self).__init__(context) diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index fe5c7b87..c857d67e 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -43,7 +43,7 @@ def _evaluate_for_annotation(context, annotation, index=None): and we're interested in that index """ context_set = context.eval_node(_fix_forward_reference(context, annotation)) - return context_set.execute_evaluated() + return context_set.execute_annotation() def _evaluate_annotation_string(context, string, index=None): @@ -57,7 +57,7 @@ def _evaluate_annotation_string(context, string, index=None): lambda context: context.array_type == u'tuple' # noqa and len(list(context.py__iter__())) >= index ).py__simple_getitem__(index) - return context_set.execute_evaluated() + return context_set.execute_annotation() def _fix_forward_reference(context, node):