Implement a lot more for typing

This commit is contained in:
Dave Halter
2018-08-24 01:13:54 +02:00
parent 9fe9bed1c9
commit 05cf6af546
8 changed files with 200 additions and 67 deletions

View File

@@ -71,6 +71,17 @@ class Context(BaseContext):
return f.filter_name(filters) return f.filter_name(filters)
return f.find(filters, attribute_lookup=not search_global) 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): def execute_annotation(self):
return execute_evaluated(self) return execute_evaluated(self)
@@ -171,24 +182,13 @@ class ContextualizedName(ContextualizedNode):
def _get_item(context, index_contexts, contextualized_node): def _get_item(context, index_contexts, contextualized_node):
from jedi.evaluate.compiled import CompiledObject 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. # The actual getitem call.
simple_getitem = getattr(context, 'py__simple_getitem__', None) 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() result = ContextSet()
unused_contexts = set()
for index_context in index_contexts: for index_context in index_contexts:
if simple_getitem is not None: if simple_getitem is not None:
index = index_context index = index_context
@@ -207,11 +207,16 @@ def _get_item(context, index_contexts, contextualized_node):
except SimpleGetItemNotFound: except SimpleGetItemNotFound:
pass pass
unused_contexts.add(index_context)
# The index was somehow not good enough or simply a wrong type. # The index was somehow not good enough or simply a wrong type.
# Therefore we now iterate through all the contexts and just take # Therefore we now iterate through all the contexts and just take
# all results. # all results.
if getitem is not None: if unused_contexts or not index_contexts:
result |= getitem(index_context, contextualized_node) result |= context.py__getitem__(
ContextSet.from_set(unused_contexts),
contextualized_node
)
return result return result

View File

@@ -163,7 +163,7 @@ class CompiledObject(Context):
return ContextSet(create_from_access_path(self.evaluator, access)) return ContextSet(create_from_access_path(self.evaluator, access))
@CheckAttribute() @CheckAttribute()
def py__getitem__(self, index_context, contextualized_node): def py__getitem__(self, index_context_set, contextualized_node):
return ContextSet.from_iterable( return ContextSet.from_iterable(
create_from_access_path(self.evaluator, access) create_from_access_path(self.evaluator, access)
for access in self.access_handle.py__getitem__all_values() for access in self.access_handle.py__getitem__all_values()

View File

@@ -202,7 +202,7 @@ class Sequence(BuiltinOverwrite, IterableMixin):
def parent(self): def parent(self):
return self.evaluator.builtins_module 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': if self.array_type == 'dict':
return self._dict_values() return self._dict_values()
return iterate_contexts(ContextSet(self)) return iterate_contexts(ContextSet(self))

View File

@@ -218,3 +218,11 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)):
@property @property
def name(self): def name(self):
return ContextName(self, self.tree_node.name) 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)

View File

@@ -6,9 +6,10 @@ contexts.
from parso.python import tree from parso.python import tree
from jedi import debug from jedi import debug
from jedi.evaluate.compiled import builtin_from_name from jedi.evaluate.compiled import builtin_from_name, CompiledObject
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context
from jedi.evaluate.context.iterable import SequenceLiteralContext 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() _PROXY_TYPES = 'Optional Union Callable Type ClassVar Tuple Generic Protocol'.split()
_TYPE_ALIAS_TYPES = 'List Dict DefaultDict Set FrozenSet Counter Deque ChainMap'.split() _TYPE_ALIAS_TYPES = 'List Dict DefaultDict Set FrozenSet Counter Deque ChainMap'.split()
@@ -26,24 +27,20 @@ class _TypingBase(object):
return '%s(%s)' % (self.__class__.__name__, self._context) return '%s(%s)' % (self.__class__.__name__, self._context)
class TypingModuleWrapper(_TypingBase): class TypingModuleName(NameWrapper):
def py__getattribute__(self, name_or_str, *args, **kwargs): def infer(self):
result = self._context.py__getattribute__(name_or_str) return ContextSet.from_iterable(
if kwargs.get('is_goto'): self._remap(context) for context in self._wrapped_name.infer()
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)
def _remap(self, context):
def _remap(context, name): name = self.string_name
if name in _PROXY_TYPES: print('name', name)
if name in (_PROXY_TYPES + _TYPE_ALIAS_TYPES):
print('NAME', name)
return TypingProxy(name, context) return TypingProxy(name, context)
elif name in _TYPE_ALIAS_TYPES:
# TODO
raise NotImplementedError
elif name == 'TypeVar': elif name == 'TypeVar':
raise NotImplementedError return TypeVarClass(context.evaluator)
return TypeVar(context)
elif name == 'Any': elif name == 'Any':
return Any(context) return Any(context)
elif name == 'TYPE_CHECKING': elif name == 'TYPE_CHECKING':
@@ -66,11 +63,18 @@ def _remap(context, name):
return context return context
class TypingModuleFilterWrapper(FilterWrapper):
name_wrapper_class = TypingModuleName
class TypingProxy(_TypingBase): class TypingProxy(_TypingBase):
py__simple_getitem__ = None py__simple_getitem__ = None
def py__getitem__(self, index_context, contextualized_node): def py__getitem__(self, index_context_set, contextualized_node):
return ContextSet(TypingProxyWithIndex(self._name, self._context, index_context)) return ContextSet.from_iterable(
TypingProxyWithIndex(self._name, self._context, index_context)
for index_context in index_context_set
)
class _WithIndexBase(_TypingBase): class _WithIndexBase(_TypingBase):
@@ -94,6 +98,10 @@ class _WithIndexBase(_TypingBase):
class TypingProxyWithIndex(_WithIndexBase): class TypingProxyWithIndex(_WithIndexBase):
def execute_annotation(self): def execute_annotation(self):
name = self._name name = self._name
if name in _TYPE_ALIAS_TYPES:
debug.warning('type aliases are not yet implemented')
return NO_CONTEXTS
if name == 'Union': if name == 'Union':
# This is kind of a special case, because we have Unions (in Jedi # This is kind of a special case, because we have Unions (in Jedi
# ContextSets). # ContextSets).
@@ -181,4 +189,71 @@ class Any(_TypingBase):
# Any is basically object, when it comes to type inference/completions. # Any is basically object, when it comes to type inference/completions.
# This is obviously not correct, but let's just use this for now. # This is obviously not correct, but let's just use this for now.
context = ContextSet(builtin_from_name(self.evaluator, u'object')) 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)

View File

@@ -289,7 +289,7 @@ def infer_param(execution_context, param):
class_context = execution_context.var_args.instance.class_context class_context = execution_context.var_args.instance.class_context
types |= eval_docstring(class_context.py__doc__()) 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 return types

View File

@@ -158,6 +158,37 @@ class AbstractFilter(object):
raise NotImplementedError 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): class AbstractUsedNamesFilter(AbstractFilter):
name_class = TreeNameDefinition name_class = TreeNameDefinition

View File

@@ -10,7 +10,7 @@ from jedi.evaluate.base_context import ContextSet, iterator_to_context_set
from jedi.evaluate.filters import AbstractTreeName, ParserTreeFilter, \ from jedi.evaluate.filters import AbstractTreeName, ParserTreeFilter, \
TreeNameDefinition TreeNameDefinition
from jedi.evaluate.context import ModuleContext, FunctionContext, ClassContext 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.compiled import CompiledObject
from jedi.evaluate.syntax_tree import tree_name_to_contexts from jedi.evaluate.syntax_tree import tree_name_to_contexts
from jedi.evaluate.utils import to_list from jedi.evaluate.utils import to_list
@@ -149,13 +149,15 @@ class TypeshedPlugin(BasePlugin):
# TODO maybe empty cache? # TODO maybe empty cache?
pass pass
else: else:
if import_names == ('typing',):
module_cls = TypingModuleWrapper
else:
module_cls = StubOnlyModuleContext
# TODO use code_lines # TODO use code_lines
stub_module_context = StubOnlyModuleContext( stub_module_context = module_cls(
context_set, evaluator, stub_module_node, path, code_lines=[] context_set, evaluator, stub_module_node, path, code_lines=[]
) )
modules = _merge_modules(context_set, stub_module_context) 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) return ContextSet.from_iterable(modules)
# If no stub is found, just return the default. # If no stub is found, just return the default.
return context_set return context_set
@@ -300,7 +302,11 @@ class StubModuleContext(_StubContextFilterMixin, ModuleContext):
class StubClassContext(_StubContextFilterMixin, ClassContext): 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): class StubFunctionContext(_MixedStubContextMixin, FunctionContext):
@@ -337,3 +343,11 @@ class StubOnlyModuleContext(ModuleContext):
) )
for f in filters: for f in filters:
yield f 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