diff --git a/jedi/evaluate/base_context.py b/jedi/evaluate/base_context.py index f3308d65..b6039bf6 100644 --- a/jedi/evaluate/base_context.py +++ b/jedi/evaluate/base_context.py @@ -71,6 +71,17 @@ class Context(BaseContext): return f.filter_name(filters) return f.find(filters, attribute_lookup=not search_global) + def py__getitem__(self, index_context_set, contextualized_node): + from jedi.evaluate import analysis + # TODO this context is probably not right. + analysis.add( + contextualized_node.context, + 'type-error-not-subscriptable', + contextualized_node.node, + message="TypeError: '%s' object is not subscriptable" % self + ) + return NO_CONTEXTS + def execute_annotation(self): return execute_evaluated(self) @@ -171,24 +182,13 @@ class ContextualizedName(ContextualizedNode): def _get_item(context, index_contexts, contextualized_node): from jedi.evaluate.compiled import CompiledObject - from jedi.evaluate.context.iterable import Slice, Sequence + from jedi.evaluate.context.iterable import Slice # The actual getitem call. simple_getitem = getattr(context, 'py__simple_getitem__', None) - getitem = getattr(context, 'py__getitem__', None) - - if getitem is None and simple_getitem is None: - from jedi.evaluate import analysis - # TODO this context is probably not right. - analysis.add( - contextualized_node.context, - 'type-error-not-subscriptable', - contextualized_node.node, - message="TypeError: '%s' object is not subscriptable" % context - ) - return NO_CONTEXTS result = ContextSet() + unused_contexts = set() for index_context in index_contexts: if simple_getitem is not None: index = index_context @@ -207,11 +207,16 @@ def _get_item(context, index_contexts, contextualized_node): except SimpleGetItemNotFound: pass - # The index was somehow not good enough or simply a wrong type. - # Therefore we now iterate through all the contexts and just take - # all results. - if getitem is not None: - result |= getitem(index_context, contextualized_node) + unused_contexts.add(index_context) + + # The index was somehow not good enough or simply a wrong type. + # Therefore we now iterate through all the contexts and just take + # all results. + if unused_contexts or not index_contexts: + result |= context.py__getitem__( + ContextSet.from_set(unused_contexts), + contextualized_node + ) return result diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index 728f9d2b..0ad9631f 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -163,7 +163,7 @@ class CompiledObject(Context): return ContextSet(create_from_access_path(self.evaluator, access)) @CheckAttribute() - def py__getitem__(self, index_context, contextualized_node): + def py__getitem__(self, index_context_set, contextualized_node): return ContextSet.from_iterable( create_from_access_path(self.evaluator, access) for access in self.access_handle.py__getitem__all_values() diff --git a/jedi/evaluate/context/iterable.py b/jedi/evaluate/context/iterable.py index 07ba8fce..10a4dc2f 100644 --- a/jedi/evaluate/context/iterable.py +++ b/jedi/evaluate/context/iterable.py @@ -202,7 +202,7 @@ class Sequence(BuiltinOverwrite, IterableMixin): def parent(self): return self.evaluator.builtins_module - def py__getitem__(self, index_context, contextualized_node): + def py__getitem__(self, index_context_set, contextualized_node): if self.array_type == 'dict': return self._dict_values() return iterate_contexts(ContextSet(self)) diff --git a/jedi/evaluate/context/klass.py b/jedi/evaluate/context/klass.py index a657ca3b..079413e3 100644 --- a/jedi/evaluate/context/klass.py +++ b/jedi/evaluate/context/klass.py @@ -218,3 +218,11 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)): @property def name(self): return ContextName(self, self.tree_node.name) + + def py__getitem__(self, index_context_set, contextualized_node): + for cls in list(self.py__mro__()): + pass + print('ha', self, list(self.py__mro__())) + + #print(index_context_set) + return super(ClassContext, self).py__getitem__(index_context_set, contextualized_node) diff --git a/jedi/evaluate/context/typing.py b/jedi/evaluate/context/typing.py index 482fba80..80da878c 100644 --- a/jedi/evaluate/context/typing.py +++ b/jedi/evaluate/context/typing.py @@ -6,9 +6,10 @@ contexts. from parso.python import tree from jedi import debug -from jedi.evaluate.compiled import builtin_from_name -from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS +from jedi.evaluate.compiled import builtin_from_name, CompiledObject +from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context from jedi.evaluate.context.iterable import SequenceLiteralContext +from jedi.evaluate.filters import FilterWrapper, NameWrapper _PROXY_TYPES = 'Optional Union Callable Type ClassVar Tuple Generic Protocol'.split() _TYPE_ALIAS_TYPES = 'List Dict DefaultDict Set FrozenSet Counter Deque ChainMap'.split() @@ -26,51 +27,54 @@ class _TypingBase(object): return '%s(%s)' % (self.__class__.__name__, self._context) -class TypingModuleWrapper(_TypingBase): - def py__getattribute__(self, name_or_str, *args, **kwargs): - result = self._context.py__getattribute__(name_or_str) - if kwargs.get('is_goto'): - return result - name = name_or_str.value if isinstance(name_or_str, tree.Name) else name_or_str - return ContextSet.from_iterable(_remap(c, name) for c in result) +class TypingModuleName(NameWrapper): + def infer(self): + return ContextSet.from_iterable( + self._remap(context) for context in self._wrapped_name.infer() + ) + + def _remap(self, context): + name = self.string_name + print('name', name) + if name in (_PROXY_TYPES + _TYPE_ALIAS_TYPES): + print('NAME', name) + return TypingProxy(name, context) + elif name == 'TypeVar': + return TypeVarClass(context.evaluator) + 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 -def _remap(context, name): - if name in _PROXY_TYPES: - return TypingProxy(name, 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 TypingModuleFilterWrapper(FilterWrapper): + name_wrapper_class = TypingModuleName class TypingProxy(_TypingBase): py__simple_getitem__ = None - def py__getitem__(self, index_context, contextualized_node): - return ContextSet(TypingProxyWithIndex(self._name, self._context, index_context)) + def py__getitem__(self, index_context_set, contextualized_node): + return ContextSet.from_iterable( + TypingProxyWithIndex(self._name, self._context, index_context) + for index_context in index_context_set + ) class _WithIndexBase(_TypingBase): @@ -94,6 +98,10 @@ class _WithIndexBase(_TypingBase): class TypingProxyWithIndex(_WithIndexBase): def execute_annotation(self): name = self._name + if name in _TYPE_ALIAS_TYPES: + debug.warning('type aliases are not yet implemented') + return NO_CONTEXTS + if name == 'Union': # This is kind of a special case, because we have Unions (in Jedi # ContextSets). @@ -181,4 +189,71 @@ class Any(_TypingBase): # 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) + super(Any, self).__init__(context) + + def execute_annotation(self): + debug.warning('Used Any, which is not implemented, yet.') + return NO_CONTEXTS + + +class GenericClass(object): + def __init__(self, class_context, ): + self._class_context = class_context + + def __getattr__(self, name): + return getattr(self._class_context, name) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self._class_context) + + +class TypeVarClass(Context): + def py__call__(self, arguments): + unpacked = arguments.unpack() + + key, lazy_context = next(unpacked, (None, None)) + name = self._find_name(lazy_context) + # The name must be given, otherwise it's useless. + if name is None or key is not None: + debug.warning('Found a variable without a name %s', arguments) + return NO_CONTEXTS + + return ContextSet(TypeVar(self.evaluator, name, unpacked)) + + def _find_name(self, lazy_context): + if lazy_context is None: + return None + + context_set = lazy_context.infer() + if not context_set: + return None + if len(context_set) > 1: + debug.warning('Found multiple contexts for a type variable: %s', context_set) + + name_context = next(iter(context_set)) + if isinstance(name_context, CompiledObject): + return name_context.get_safe_value(default=None) + return None + + +class TypeVar(Context): + def __init__(self, evaluator, name, unpacked_args): + super(TypeVar, self).__init__(evaluator) + self._name = name + self._unpacked_args = unpacked_args + + def _unpack(self): + # TODO + constraints = ContextSet() + bound = None + covariant = False + contravariant = False + for key, lazy_context in unpacked: + if key is None: + constraints |= lazy_context.infer() + else: + if name == 'bound': + bound = lazy_context.infer() + + def __repr__(self): + return '<%s: %s>' % (self.__class__.__name__, self._name) diff --git a/jedi/evaluate/docstrings.py b/jedi/evaluate/docstrings.py index 032f9d3c..d4002f98 100644 --- a/jedi/evaluate/docstrings.py +++ b/jedi/evaluate/docstrings.py @@ -289,7 +289,7 @@ def infer_param(execution_context, param): class_context = execution_context.var_args.instance.class_context types |= eval_docstring(class_context.py__doc__()) - debug.dbg('Found param types for docstring %s', types, color='BLUE') + debug.dbg('Found param types for docstring: %s', types, color='BLUE') return types diff --git a/jedi/evaluate/filters.py b/jedi/evaluate/filters.py index 6f4f0599..955f1ced 100644 --- a/jedi/evaluate/filters.py +++ b/jedi/evaluate/filters.py @@ -158,6 +158,37 @@ class AbstractFilter(object): raise NotImplementedError +class FilterWrapper(object): + name_wrapper_class = None + + def __init__(self, wrapped_filter): + self._wrapped_filter = wrapped_filter + + def wrap_names(self, names): + return [self.name_wrapper_class(name) for name in names] + + def get(self, name): + return self.wrap_names(self._wrapped_filter.get(name)) + + def values(self, name): + return self.wrap_names(self._wrapped_filter.values()) + + +class NameWrapper(object): + def __init__(self, wrapped_name): + self._wrapped_name = wrapped_name + + @abstractmethod + def infer(self): + raise NotImplementedError + + def __getattr__(self, name): + return getattr(self._wrapped_name, name) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self._wrapped_name) + + class AbstractUsedNamesFilter(AbstractFilter): name_class = TreeNameDefinition diff --git a/jedi/plugins/typeshed.py b/jedi/plugins/typeshed.py index bddb9e9d..650b50a2 100644 --- a/jedi/plugins/typeshed.py +++ b/jedi/plugins/typeshed.py @@ -10,7 +10,7 @@ from jedi.evaluate.base_context import ContextSet, iterator_to_context_set from jedi.evaluate.filters import AbstractTreeName, ParserTreeFilter, \ TreeNameDefinition from jedi.evaluate.context import ModuleContext, FunctionContext, ClassContext -from jedi.evaluate.context.typing import TypingModuleWrapper +from jedi.evaluate.context.typing import TypingModuleFilterWrapper from jedi.evaluate.compiled import CompiledObject from jedi.evaluate.syntax_tree import tree_name_to_contexts from jedi.evaluate.utils import to_list @@ -149,13 +149,15 @@ class TypeshedPlugin(BasePlugin): # TODO maybe empty cache? pass else: + if import_names == ('typing',): + module_cls = TypingModuleWrapper + else: + module_cls = StubOnlyModuleContext # TODO use code_lines - stub_module_context = StubOnlyModuleContext( + stub_module_context = module_cls( context_set, evaluator, stub_module_node, path, code_lines=[] ) modules = _merge_modules(context_set, stub_module_context) - if import_names == ('typing',): - modules = [TypingModuleWrapper('typing', m) for m in modules] return ContextSet.from_iterable(modules) # If no stub is found, just return the default. return context_set @@ -300,7 +302,11 @@ class StubModuleContext(_StubContextFilterMixin, ModuleContext): class StubClassContext(_StubContextFilterMixin, ClassContext): - pass + def __getattribute__(self, name): + if name == ('py__getitem__', 'py__bases__'): + # getitem is always done in the stub class. + return getattr(self.stub_context, name) + return super(StubClassContext, self).__getattribute__(name) class StubFunctionContext(_MixedStubContextMixin, FunctionContext): @@ -337,3 +343,11 @@ class StubOnlyModuleContext(ModuleContext): ) for f in filters: yield f + + +class TypingModuleWrapper(StubOnlyModuleContext): + def get_filters(self, *args, **kwargs): + filters = super(TypingModuleWrapper, self).get_filters(*args, **kwargs) + yield TypingModuleFilterWrapper(next(filters)) + for f in filters: + yield f